Category: orientdb

  • Khóa học miễn phí OrientDB – Caching nhận dự án làm có lương

    OrientDB – Caching



    Caching is a concept that will create a copy of the database table structure providing a comfortable environment for the user applications. OrientDB has several caching mechanisms at different levels.

    The following illustration gives an idea about what caching is.

    Caching Mechanisms

    In the above illustration DB1, DB2, DB3 are the three different database instances used in an application.

    Level-1 cache is a Local cache which stores all the entities known by a specific session. If you have three transactions in this session, it will hold all entities used by all three transactions. This cache gets cleared when you close the session or when you perform the “clear” method. It reduces the burden of the I/O operations between the application and the database and in turn increases the performance.

    Level-2 cache is a Real cache that works by using third party provider. You can have full control over the contents of the cache, i.e. you will be able to specify which entries should be removed, which ones should be stored longer and so on. It is a full shared cache among multiple threads.

    Storage model is nothing but storage device that is disk, memory, or remote server.

    How Cache Works in OrientDB?

    OrientDB caching provides different methodologies in different environments. Caching is mainly used for faster database transactions, reducing the processing time of a transaction and increasing the performance. The following flow diagrams show how caching works in local mode and client-server mode.

    Local Mode (Embedded Database)

    The following flow diagram tells you how the record is in-between storage and used application in the local mode i.e., when your database server is in your localhost.

    Embedded Database

    When the client application asks for a record OrientDB checks for the following −

    • If a transaction has begun, then it searches inside the transaction for changed records and returns it if found.

    • If the local cache is enabled and contains the requested record, then returns it.

    • If at this point the record is not in cache, then asks for it to the Storage (disk, memory).

    Client Server Mode (Remote Database)

    The following flow diagram tells you how the record is in-between storage and used application in the client-server mode i.e., when your database server is in remote location.

    Remote Database

    When the client application asks for a record, OrientDB checks for the following −

    • If a transaction has begun, then it searches inside the transaction for changed records and returns it if found.

    • If the local cache is enabled and contains the requested record, then returns it.

    • At this point, if the record is not in cache, then asks for it to the Server through a TCP/IP call.

    • In the server, if the local cache is enabled and contains the requested record, then returns it.

    • At this point, still the record is not cached in the server, then asks for it to the Storage (disk, memory).


    Khóa học lập trình tại Toidayhoc vừa học vừa làm dự án vừa nhận lương: Khóa học lập trình nhận lương tại trung tâm Toidayhoc

  • Khóa học miễn phí OrientDB – Performance Tuning nhận dự án làm có lương

    OrientDB – Performance Tuning



    In this chapter, you can get some general tips on how to optimize your application that uses OrientDB. There are three ways to increase the performance for different types of database.

    • Document Database Performance Tuning − It uses a technique that helps avoid document creation for every new document.

    • Object Database Performance Tuning − It uses the generic techniques to improve performance.

    • Distributed Configuration Tuning − It uses different methodologies to improve performance in distributed configuration.

    You can achieve generic performance tuning by changing the Memory, JVM, and Remote connection settings.

    Memory Settings

    There are different strategies in memory setting to improve performance.

    Server and Embedded Settings

    These settings are valid for both Server component and the JVM where the Java application is run using OrientDB in Embedded mode, by directly using plocal.

    The most important thing on tuning is assuring the memory settings are correct. What can make a real difference is the right balancing between the heap and the virtual memory used by Memory Mapping, especially on large datasets (GBs, TBs and more) where the inmemory cache structures count less than raw IO.

    For example, if you can assign maximum 8GB to the Java process, it”s usually better assigning small heap and large disk cache buffer (off-heap memory).

    Try the following command to increase the heap memory.

    java -Xmx800m -Dstorage.diskCache.bufferSize=7200 ...
    

    The storage.diskCache.bufferSize setting (with old “local” storage it was file.mmap.maxMemory) is in MB and tells how much memory to use for Disk Cache component. By default it is 4GB.

    NOTE − If the sum of maximum heap and disk cache buffer is too high, it could cause the OS to swap with huge slowdown.

    JVM Settings

    JVM settings are encoded in server.sh (and server.bat) batch files. You can change them to tune the JVM according to your usage and hw/sw settings. Add the following line in server.bat file.

    -server -XX:+PerfDisableSharedMem
    

    This setting will disable writing debug information about the JVM. In case you need to profile the JVM, just remove this setting.

    Remote Connections

    There are many ways to improve performance when you access the database using a remote connection.

    Fetching Strategy

    When you work with a remote database you have to pay attention to the fetching strategy used. By default, OrientDB client loads only the record contained in the resultset. For example, if a query returns 100 elements, but if you cross these elements from the client, then OrientDB client lazily loads the elements with one more network call to the server for each missed record.

    Network Connection Pool

    Each client, by default, uses only one network connection to talk with the server. Multiple threads on the same client share the same network connection pool.

    When you have multiple threads, there could be a bottleneck since a lot of time is spent waiting for a free network connection. This is the reason why it is important to configure the network connection pool.

    The configuration is very simple, just 2 parameters −

    • minPool − It is the initial size of the connection pool. The default value is configured as global parameters “client.channel.minPool”.

    • maxPool − It is the maximum size the connection pool can reach. The default value is configured as global parameters “client.channel.maxPool”.

    If all the pool connections are busy, then the client thread will wait for the first free connection.

    Example command of configuration by using database properties.

    database = new ODatabaseDocumentTx("remote:localhost/demo");
    database.setProperty("minPool", 2);
    database.setProperty("maxPool", 5);
    
    database.open("admin", "admin");
    

    Distributed Configuration Tuning

    There are many ways to improve performance on distributed configuration.

    Use Transactions

    Even when you update graphs, you should always work in transactions. OrientDB allows you to work outside of them. Common cases are read-only queries or massive and nonconcurrent operations can be restored in case of failure. When you run on distributed configuration, using transactions helps to reduce latency. This is because the distributed operation happens only at commit time. Distributing one big operation is much efficient than transferring small multiple operations, because of the latency.

    Replication vs Sharding

    OrientDB distributed configuration is set to full replication. Having multiple nodes with the same copy of database is important for scale reads. In fact, each server is independent on executing reads and queries. If you have 10 server nodes, the read throughput is 10x.

    With writes, it”s the opposite: having multiple nodes with full replication slows down the operations, if the replication is synchronous. In this case, sharding the database across multiple nodes allows you to scale up writes, because only a subset of nodes are involved on write. Furthermore, you could have a database bigger than one server node HD.

    Scale up on Writes

    If you have a slow network and you have a synchronous (default) replication, you could pay the cost of latency. In fact when OrientDB runs synchronously, it waits at least for the writeQuorum. This means that if the writeQuorum is 3, and you have 5 nodes, the coordinator server node (where the distributed operation is started) has to wait for the answer from at least 3 nodes in order to provide the answer to the client.

    In order to maintain the consistency, the writeQuorum should be set to the majority. If you have 5 nodes the majority is 3. With 4 nodes, it is still 3. Setting the writeQuorum to 3 instead of 4 or 5 allows to reduce the latency cost and still maintain the consistency.

    Asynchronous Replication

    To speed things up, you can set up Asynchronous Replication to remove the latency bottleneck. In this case, the coordinator server node executes the operation locally and gives the answer to the client. The entire replication will be in the background. In case the quorum is not reached, the changes will be rolled back transparently.

    Scale up on Reads

    If you already set the writeQuorum to the majority of nodes, you can leave the readQuorum to 1 (the default). This speeds up all the reads.


    Khóa học lập trình tại Toidayhoc vừa học vừa làm dự án vừa nhận lương: Khóa học lập trình nhận lương tại trung tâm Toidayhoc

  • Khóa học miễn phí OrientDB – Security nhận dự án làm có lương

    OrientDB – Security



    Like RDBMS, OrientDB also provides security based on well-known concepts, users, and roles. Each database has its own users and each user has one or more roles. Roles are the combination of working modes and set of permissions.

    Users

    By default OrientDB maintains three different users for all database in the server −

    • Admin − This user has access to all functions on the database without limitation.

    • Reader − This user is a read-only user. The reader can query any records in the database, but can”t modify or delete them. It has no access to internal information, such as the users and roles themselves.

    • Writer − This user is the same as the user reader, but it can also create, update, and delete records.

    Working with Users

    When you are connected to a database, you can query the current users on the database by using SELECT queries on the OUser class.

    orientdb> SELECT RID, name, status FROM OUser
    

    If the above query is executed successfully, you will get the following output.

    ---+--------+--------+--------
    #  | @CLASS | name   | status
    ---+--------+--------+--------
    0  | null   | admin  | ACTIVE
    1  | null   | reader | ACTIVE
    2  | null   | writer | ACTIVE
    ---+--------+--------+--------
    3 item(s) found. Query executed in 0.005 sec(s).
    

    Creating a New User

    To create a new user, use the INSERT command. Remember, in doing so, you must set the status to ACTIVE and give it a valid role.

    orientdb> INSERT INTO OUser SET
                   name = ''jay'',
                   password = ''JaY'',
                   status = ''ACTIVE'',
                   roles = (SELECT FROM ORole WHERE name = ''reader'')
    

    Updating Users

    You can change the name for the user with the UPDATE statement.

    orientdb> UPDATE OUser SET name = ''jay'' WHERE name = ''reader''
    

    In the same way, you can also change the password for the user.

    orientdb> UPDATE OUser SET password = ''hello'' WHERE name = ''reader''
    

    OrientDB saves the password in a hash format. The trigger OUserTrigger encrypts the password transparently before it saves the record.

    Disabling Users

    To disable a user, use UPDATE to switch its status from ACTIVE to SUSPENDED. For instance, if you want to disable all users except for admin, use the following command −

    orientdb> UPDATE OUser SET status = ''SUSPENDED'' WHERE name <> ''admin''
    

    Roles

    A role determines what operations a user can perform against a resource. Mainly, this decision depends on the working mode and the rules. The rules themselves work differently, depending on the working mode.

    Working with Roles

    When you are connected to a database, you can query the current roles on the database using SELECT queries on the ORole class.

    orientdb> SELECT RID, mode, name, rules FROM ORole
    

    If the above query is executed successfully, you will get the following output.

    --+------+----+--------+-------------------------------------------------------
    # |@CLASS|mode| name   | rules
    --+------+----+--------+-------------------------------------------------------
    0 | null | 1  | admin  | {database.bypassRestricted = 15}
    1 | null | 0  | reader | {database.cluster.internal = 2, database.cluster.orole = 0...
    2 | null | 0  | writer | {database.cluster.internal = 2, database.cluster.orole = 0...
    --+------+----+--------+-------------------------------------------------------
    3 item(s) found.  Query executed in 0.002 sec(s).
    

    Creating New Roles

    To create a new role, use the INSERT statement.

    orientdb> INSERT INTO ORole SET name = ''developer'', mode = 0
    

    Working with Modes

    Where rules determine what users belonging to certain roles can do on the databases, working modes determine how OrientDB interprets these rules. There are two types of working modes, designated by 1 and 0.

    • Allow All But (Rules) − By default it is the super user mode. Specify exceptions to this using the rules. If OrientDB finds no rules for a requested resource, then it allows the user to execute the operation. Use this mode mainly for power users and administrators. The default role admin uses this mode by default and has no exception rules. It is written as 1 in the database.

    • Deny All But (Rules) − By default this mode allows nothing. Specify exceptions to this using the rules. If OrientDB finds rules for a requested resource, then it allows the user to execute the operation. Use this mode as the default for all classic users. The default roles, reader and writer, use this mode. It is written as 0 in the database.


    Khóa học lập trình tại Toidayhoc vừa học vừa làm dự án vừa nhận lương: Khóa học lập trình nhận lương tại trung tâm Toidayhoc

  • Khóa học miễn phí OrientDB – Export Record nhận dự án làm có lương

    OrientDB – Export Record



    Export Record is the command used to export the loaded record into the requested and supported format. If you are executing any wrong syntax, it will give the list of supported formats. OrientDB is a family of Document database, therefore JSON is the default supported format.

    The following statement is the basic syntax of the Export Record command.

    EXPORT RECORD <format>
    

    Where <Format> defines the format you want to get the record.

    Note − Export command will export the loaded record based on Record ID.

    Example

    Let us consider the same Customer table that we have used in the previous chapter.

    Sr.No. Name Age
    1 Satish 25
    2 Krishna 26
    3 Kiran 29
    4 Javeed 21
    5 Raja 29

    Try the following query to retrieve the record having Record ID @rid: #11:0.

    orientdb {db = demo}> LOAD RECORD #11:0
    

    If the above query is executed successfully, you will get the following output.

    +---------------------------------------------------------------------------+
    | Document - @class: Customer        @rid: #11:0           @version: 1      |
    +---------------------------------------------------------------------------+
    |                     Name | Value                                          |
    +---------------------------------------------------------------------------+
    |                       id | 1                                              |
    |                     name | satish                                         |
    |                      age | 25                                             |
    +---------------------------------------------------------------------------+
    

    Use the following query to export he loaded record (#11:0) into JSON format.

    orientdb {db = demo}> EXPORT RECORD json
    

    If the above query is executed successfully, you will get the following output.

    {
       "@type": "d",
          "@rid": "#11:0",
       "@version": 1,
       "@class": "Customer",
          "id": 1,
          "name": "satish",
          "age": 25
    }
    

    Khóa học lập trình tại Toidayhoc vừa học vừa làm dự án vừa nhận lương: Khóa học lập trình nhận lương tại trung tâm Toidayhoc

  • Khóa học miễn phí OrientDB – Load Record nhận dự án làm có lương

    OrientDB – Load Record



    Load Record is used to load a particular record from the schema. Load record will load the record with the help of Record ID. It is represented with @rid symbol in the resultset.

    The following statement is the basic syntax of the LOAD Record command.

    LOAD RECORD <record-id>
    

    Where <record-id> defines the record id of the record you want to load.

    If you don’t know the Record ID of a particular record, then you can execute any query against the table. In the result-set you will find the Record ID (@rid) of the respective record.

    Example

    Let us consider the same Customer table that we have used in previous chapters.

    Sr.No. Name Age
    1 Satish 25
    2 Krishna 26
    3 Kiran 29
    4 Javeed 21
    5 Raja 29

    Try the following query to retrieve the record having Record ID @rid: #11:0.

    orientdb {db = demo}> LOAD RECORD #11:0
    

    If the above query is executed successfully, you will get the following output.

    +---------------------------------------------------------------------------+
    | Document - @class: Customer        @rid: #11:0           @version: 1      |
    +---------------------------------------------------------------------------+
    |                     Name | Value                                          |
    +---------------------------------------------------------------------------+
    |                       id | 1                                              |
    |                     name | satish                                         |
    |                      age | 25                                             |
    +---------------------------------------------------------------------------+
    

    Khóa học lập trình tại Toidayhoc vừa học vừa làm dự án vừa nhận lương: Khóa học lập trình nhận lương tại trung tâm Toidayhoc

  • Khóa học miễn phí OrientDB – Reload Record nhận dự án làm có lương

    OrientDB – Reload Record



    Reload Record also works similar to Load Record command and is also used to load a particular record from the schema. Load record will load the record with the help of Record ID. It is represented with @rid symbol in the result-set. The main difference is Reload record ignores the cache which is useful when external concurrent transactions is applied to change the record. It will give the latest update.

    The following statement is the basic syntax of the RELOAD Record command.

    RELOAD RECORD <record-id>
    

    Where <record-id> defines the record id of the record you want to reload.

    If you don’t know the Record ID of a particular record, then you can execute any query against the table. In the result-set you will find the Record ID (@rid) of the respective record.

    Example

    Let us consider the same Customer table that we have used in the previous chapter.

    Sr.No. Name Age
    1 Satish 25
    2 Krishna 26
    3 Kiran 29
    4 Javeed 21
    5 Raja 29

    Try the following query to retrieve the record having Record ID @rid: #11:0.

    orientdb {db = demo}> LOAD RECORD #11:0
    

    If the above query is executed successfully, you will get the following output.

    +---------------------------------------------------------------------------+
    | Document - @class: Customer        @rid: #11:0           @version: 1      |
    +---------------------------------------------------------------------------+
    |                     Name | Value                                          |
    +---------------------------------------------------------------------------+
    |                       id | 1                                              |
    |                     name | satish                                         |
    |                      age | 25                                             |
    +---------------------------------------------------------------------------+
    

    Khóa học lập trình tại Toidayhoc vừa học vừa làm dự án vừa nhận lương: Khóa học lập trình nhận lương tại trung tâm Toidayhoc

  • Khóa học miễn phí OrientDB – Truncate Record nhận dự án làm có lương

    OrientDB – Truncate Record



    Truncate Record command is used to delete the values of a particular record.

    The following statement is the basic syntax of the Truncate command.

    TRUNCATE RECORD <rid>*
    

    Where <rid>* indicates the Record ID to truncate. You can use multiple Rids separated by comma to truncate multiple records. It returns the number of records truncated.

    Example

    Let us consider the same Customer table that we have used in the previous chapter.

    Sr.No. Name Age
    1 Satish 25
    2 Krishna 26
    3 Kiran 29
    4 Javeed 21
    5 Raja 28

    Try the following query to truncate the record having Record ID #11:4.

    Orientdb {db = demo}> TRUNCATE RECORD #11:4
    

    If the above query is executed successfully, you will get the following output.

    Truncated 1 record(s) in 0.008000 sec(s).
    

    To check the record of Customer table you can use the following query.

    Orientdb {db = demo}> SELECT FROM Customer
    

    If the above query is executed successfully, you will get the following output.

    ----+-----+--------+----+-------+----
    #   |@RID |@CLASS  |id  |name   |age
    ----+-----+--------+----+-------+----
    0   |#11:0|Customer|1   |satish |25
    1   |#11:1|Customer|2   |krishna|26
    2   |#11:2|Customer|3   |kiran  |29
    3   |#11:3|Customer|4   |javeed |21
    ----+-----+--------+----+-------+----
    

    Khóa học lập trình tại Toidayhoc vừa học vừa làm dự án vừa nhận lương: Khóa học lập trình nhận lương tại trung tâm Toidayhoc

  • Khóa học miễn phí OrientDB – Update Record nhận dự án làm có lương

    OrientDB – Update Record



    Update Record command is used to modify the value of a particular record. SET is the basic command to update a particular field value.

    The following statement is the basic syntax of the Update command.

    UPDATE <class>|cluster:<cluster>|<recordID>
       [SET|INCREMENT|ADD|REMOVE|PUT <field-name> = <field-value>[,]*] |[CONTENT| MERGE <JSON>]
       [UPSERT]
       [RETURN <returning> [<returning-expression>]]
       [WHERE <conditions>]
       [LOCK default|record]
       [LIMIT <max-records>] [TIMEOUT <timeout>]
    

    Following are the details about the options in the above syntax.

    SET − Defines the field to update.

    INCREMENT − Increments the specified field value by the given value.

    ADD − Adds the new item in the collection fields.

    REMOVE − Removes an item from the collection field.

    PUT − Puts an entry into map field.

    CONTENT − Replaces the record content with JSON document content.

    MERGE − Merges the record content with a JSON document.

    LOCK − Specifies how to lock the records between load and update. We have two options to specify Default and Record.

    UPSERT − Updates a record if it exists or inserts a new record if it doesn’t. It helps in executing a single query in the place of executing two queries.

    RETURN − Specifies an expression to return instead of the number of records.

    LIMIT − Defines the maximum number of records to update.

    TIMEOUT − Defines the time you want to allow the update run before it times out.

    Example

    Let us consider the same Customer table that we have used in the previous chapter.

    Sr.No. Name Age
    1 Satish 25
    2 Krishna 26
    3 Kiran 29
    4 Javeed 21
    5 Raja 29

    Try the following query to update the age of a customer ‘Raja’.

    Orientdb {db = demo}> UPDATE Customer SET age = 28 WHERE name = ''Raja''
    

    If the above query is executed successfully, you will get the following output.

    Updated 1 record(s) in 0.008000 sec(s).
    

    To check the record of Customer table you can use the following query.

    orientdb {db = demo}> SELECT FROM Customer
    

    If the above query is executed successfully, you will get the following output.

    ----+-----+--------+----+-------+----
    #   |@RID |@CLASS  |id  |name   |age
    ----+-----+--------+----+-------+----
    0   |#11:0|Customer|1   |satish |25
    1   |#11:1|Customer|2   |krishna|26
    2   |#11:2|Customer|3   |kiran  |29
    3   |#11:3|Customer|4   |javeed |21
    4   |#11:4|Customer|5   |raja   |28
    ----+-----+--------+----+-------+----
    

    Khóa học lập trình tại Toidayhoc vừa học vừa làm dự án vừa nhận lương: Khóa học lập trình nhận lương tại trung tâm Toidayhoc

  • Khóa học miễn phí OrientDB – Delete Record nhận dự án làm có lương

    OrientDB – Delete Record



    Delete Record command is used to delete one or more records completely from the database.

    The following statement is the basic syntax of the Delete command.

    DELETE FROM <Class>|cluster:<cluster>|index:<index>
       [LOCK <default|record>]
       [RETURN <returning>]
       [WHERE <Condition>*]
       [LIMIT <MaxRecords>]
       [TIMEOUT <timeout>]
    

    Following are the details about the options in the above syntax.

    LOCK − Specifies how to lock the records between load and update. We have two options to specify Default and Record.

    RETURN − Specifies an expression to return instead of the number of records.

    LIMIT − Defines the maximum number of records to update.

    TIMEOUT − Defines the time you want to allow the update run before it times out.

    Note − Don’t use DELETE to remove Vertices or Edges because it effects the integrity of the graph.

    Example

    Let us consider the Customer table.

    Sr.No. Name Age
    1 Satish 25
    2 Krishna 26
    3 Kiran 29
    4 Javeed 21

    Try the following query to delete the record having id = 4.

    orientdb {db = demo}> DELETE FROM Customer WHERE id = 4
    

    If the above query is executed successfully, you will get the following output.

    Delete 1 record(s) in 0.008000 sec(s).
    

    To check the record of Customer table you can use the following query.

    Orientdb {db = demo}> SELECT FROM Customer
    

    If the above query is executed successfully, you will get the following output.

    ----+-----+--------+----+-------+----
    #   |@RID |@CLASS  |id  |name   |age
    ----+-----+--------+----+-------+----
    0   |#11:0|Customer|1   |satish |25
    1   |#11:1|Customer|2   |krishna|26
    2   |#11:2|Customer|3   |kiran  |29
    ----+-----+--------+----+-------+----
    

    Khóa học lập trình tại Toidayhoc vừa học vừa làm dự án vừa nhận lương: Khóa học lập trình nhận lương tại trung tâm Toidayhoc

  • Khóa học miễn phí OrientDB – Create Class nhận dự án làm có lương

    OrientDB – Create Class



    OrientDB supports multi-model feature and provides different ways in approaching and understanding the basic concepts of a database. However, we can easily access these models from the perspective of Document database API. Like RDBMS, OrientDB also uses the Record as an element of storage but it uses the Document type. Documents are stored in the form of Key/Value pairs. We are storing fields and properties as key/value pairs which belong to a concepts class.

    Class is a type of data model and the concept is drawn from the Object-oriented programming paradigm. Based on the traditional document database model, data is stored in the form of collection, while in the relational database model data it is stored in tables. OrientDB follows the Document API along with OPPS paradigm. As a concept, class in OrientDB has the closest relationship with the table in relational databases, but (unlike tables) classes can be schema-less, schema-full or mixed. Classes can inherit from other classes, creating trees of classes. Each class has its own cluster or clusters, (created by default, if none are defined).

    The following statement is the basic syntax of the Create Class Command.

    CREATE CLASS <class>
    [EXTENDS <super-class>]
    [CLUSTER <cluster-id>*]
    [CLUSTERS <total-cluster-number>]
    [ABSTRACT]
    

    Following are the details about the options in the above syntax.

    <class> − Defines the name of the class you want to create.

    <super-class> − Defines the super-class you want to extend with this class.

    <total-cluster-number> − Defines the total number of clusters used in this class. Default is 1.

    ABSTARCT − Defines the class is abstract. This is optional.

    Example

    As discussed, class is a concept related to table. Therefore here we will create a table Account. However, while creating class we cannot define fields i.e., properties based on OOPS paradigm.

    The following command is to create a class named Account.

    orientdb> CREATE CLASS Account
    

    If the above command is executed successfully, you will get the following output.

    Class created successfully
    

    You can use the following command to create a class Car which extends to class Vehicle.

    orientdb> CREATE CLASS Car EXTENDS Vehicle
    

    If the above command is executed successfully, you will get the following output.

    Class created successfully
    

    You can use the following command to create a class Person as abstract.

    orientdb> CREATE CLASS Person ABSTRACT
    

    If the above command is executed successfully, you will get the following output.

    Class created successfully
    

    Note − Without having properties, the class is useless and unable to build real object. In the further chapters, you can learn how to create properties for a particular class.


    Khóa học lập trình tại Toidayhoc vừa học vừa làm dự án vừa nhận lương: Khóa học lập trình nhận lương tại trung tâm Toidayhoc