服务器之家:专注于服务器技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|JAVA教程|ASP教程|

服务器之家 - 编程语言 - JAVA教程 - 详解Java的Hibernat框架中的Map映射与SortedMap映射

详解Java的Hibernat框架中的Map映射与SortedMap映射

2020-03-12 11:20goldensun JAVA教程

这篇文章主要介绍了Java的Hibernat框架中的Map映射与SortedMap映射,Hibernat是Java的SSH三大web开发框架之一,需要的朋友可以参考下

Map映射
Map映射是一个java集合存储在键 - 值对的元素,并且不允许在列表中重复的元素。 Map接口提供三种collection视图,允许Map内容看作是一组键-值集合,或者设置键 - 值映射关系。

Map被映射到映射表中一个<map>元素和无序的地图可以在java.util.HashMap中被初始化。

定义RDBMS表:
考虑一个情况,我们需要员工记录存储在EMPLOYEE表,将有以下结构:

?
1
2
3
4
5
6
7
create table EMPLOYEE (
  id INT NOT NULL auto_increment,
  first_name VARCHAR(20) default NULL,
  last_name VARCHAR(20) default NULL,
  salary   INT default NULL,
  PRIMARY KEY (id)
);

此外,假设每个员工都可以有一个或多个与他/她相关的证书。我们将存储证书的相关信息在一个单独的表,该表具有以下结构:

?
1
2
3
4
5
6
7
create table CERTIFICATE (
  id INT NOT NULL auto_increment,
  certificate_type VARCHAR(40) default NULL,
  certificate_name VARCHAR(30) default NULL,
  employee_id INT default NULL,
  PRIMARY KEY (id)
);

有一个对多(one-to-many )的关系在EMPLOYEE和证书对象之间。

定义POJO类:
让我们实现一个POJO类员工将被用于保存与EMPLOYEE表中的对象和有证书的列表变量的集合。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import java.util.*;
 
public class Employee {
  private int id;
  private String firstName;
  private String lastName; 
  private int salary;
  private Map certificates;
 
  public Employee() {}
  public Employee(String fname, String lname, int salary) {
   this.firstName = fname;
   this.lastName = lname;
   this.salary = salary;
  }
  public int getId() {
   return id;
  }
  public void setId( int id ) {
   this.id = id;
  }
  public String getFirstName() {
   return firstName;
  }
  public void setFirstName( String first_name ) {
   this.firstName = first_name;
  }
  public String getLastName() {
   return lastName;
  }
  public void setLastName( String last_name ) {
   this.lastName = last_name;
  }
  public int getSalary() {
   return salary;
  }
  public void setSalary( int salary ) {
   this.salary = salary;
  }
 
  public Map getCertificates() {
   return certificates;
  }
  public void setCertificates( Map certificates ) {
   this.certificates = certificates;
  }
}

我们需要相应的证书表定义另一个POJO类,这样的证书对象可以存储和检索到的证书表。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Certificate{
  private int id;
  private String name;
 
  public Certificate() {}
  public Certificate(String name) {
   this.name = name;
  }
  public int getId() {
   return id;
  }
  public void setId( int id ) {
   this.id = id;
  }
  public String getName() {
   return name;
  }
  public void setName( String name ) {
   this.name = name;
  }
}

定义Hibernate映射文件:
让我们开发指示Hibernate如何定义的类映射到数据库表的映射文件。<map>元素将被用于定义所使用的映射的规则。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
 "-//Hibernate/Hibernate Mapping DTD//EN"
 "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
 
<hibernate-mapping>
  <class name="Employee" table="EMPLOYEE">
   <meta attribute="class-description">
     This class contains the employee detail.
   </meta>
   <id name="id" type="int" column="id">
     <generator class="native"/>
   </id>
   <map name="certificates" cascade="all">
     <key column="employee_id"/>
     <index column="certificate_type" type="string"/>
     <one-to-many class="Certificate"/>
   </map>
   <property name="firstName" column="first_name" type="string"/>
   <property name="lastName" column="last_name" type="string"/>
   <property name="salary" column="salary" type="int"/>
  </class>
 
  <class name="Certificate" table="CERTIFICATE">
   <meta attribute="class-description">
     This class contains the certificate records.
   </meta>
   <id name="id" type="int" column="id">
     <generator class="native"/>
   </id>
   <property name="name" column="certificate_name" type="string"/>
  </class>
 
</hibernate-mapping>

应该保存的映射文件中的格式<classname>.hbm.xml。我们保存映射文件中的文件Employee.hbm.xml。已经熟悉了大部分的映射细节,但让我们再次看映射文件中的所有元素:

映射文档是具有<hibernate-mapping>为对应于每一个类包含2个<class>元素的根元素的XML文档。

<class>元素被用于定义数据库表从一个Java类特定的映射。 Java类名指定使用class元素的name属性和使用表属性数据库表名指定。

<meta>元素是可选元素,可以用来创建类的描述。

<id>元素映射在类中的唯一ID属性到数据库表的主键。 id元素的name属性是指属性的类和column属性是指在数据库表中的列。 type属性保存了Hibernate映射类型,这种类型的映射将会从Java转换为SQL数据类型。

id元素内的<generator>元素被用来自动生成的主键值。将生成元素的class属性设置为原产于让Hibernate拿起无论是identity,sequence或者hilo中的算法来创建主键根据底层数据库的支持能力。

<property>元素用于一个Java类的属性映射到数据库表中的列。元素的name属性是指属性的类和column属性是指在数据库表中的列。 type属性保存了Hibernate映射类型,这种类型的映射将会从Java转换为SQL数据类型。

<map>元素用于设置证书和Employee类之间的关系。我们使用cascade属性的<map>元素来告诉Hibernate来保存证书的对象,同时为Employee对象。 name属性设置为定义Mapvariable在父类中,在我们的案例中是证书。

<index>的元素用于表示键/值对地图的关键部位。该键将使用一个字符串类型的存储在列certificate_type。

<key>元素是包含外键的父对象,即在证书表中的列。表EMPLOYEE。

<one-to-many>元素表示一个Employee对象涉及到很多证书的对象,并因此,证书对象必须有与Employee父类有关。可以根据需要使用<one-to-one>,<many-to-one>或<many-to-many>这个元素。

创建应用程序类:
最后,创建应用程序类的main()方法来运行应用程序。使用这个应用程序来保存员工记录地连同证书列表,然后在应用上执行CRUD操作上的记录。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
import java.util.*;
 
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
 
public class ManageEmployee {
  private static SessionFactory factory;
  public static void main(String[] args) {
   try{
     factory = new Configuration().configure().buildSessionFactory();
   }catch (Throwable ex) {
     System.err.println("Failed to create sessionFactory object." + ex);
     throw new ExceptionInInitializerError(ex);
   }
   ManageEmployee ME = new ManageEmployee();
   /* Let us have a set of certificates for the first employee */
   HashMap set = new HashMap();
   set.put("ComputerScience", new Certificate("MCA"));
   set.put("BusinessManagement", new Certificate("MBA"));
   set.put("ProjectManagement", new Certificate("PMP"));
   
   /* Add employee records in the database */
   Integer empID = ME.addEmployee("Manoj", "Kumar", 4000, set);
 
   /* List down all the employees */
   ME.listEmployees();
 
   /* Update employee's salary records */
   ME.updateEmployee(empID, 5000);
 
   /* List down all the employees */
   ME.listEmployees();
 
  }
 
  /* Method to add an employee record in the database */
  public Integer addEmployee(String fname, String lname,
                   int salary, HashMap cert){
   Session session = factory.openSession();
   Transaction tx = null;
   Integer employeeID = null;
   try{
     tx = session.beginTransaction();
     Employee employee = new Employee(fname, lname, salary);
     employee.setCertificates(cert);
     employeeID = (Integer) session.save(employee);
     tx.commit();
   }catch (HibernateException e) {
     if (tx!=null) tx.rollback();
     e.printStackTrace();
   }finally {
     session.close();
   }
   return employeeID;
  }
 
  /* Method to list all the employees detail */
  public void listEmployees( ){
   Session session = factory.openSession();
   Transaction tx = null;
   try{
     tx = session.beginTransaction();
     List employees = session.createQuery("FROM Employee").list();
     for (Iterator iterator1 =
              employees.iterator(); iterator1.hasNext();){
      Employee employee = (Employee) iterator1.next();
      System.out.print("First Name: " + employee.getFirstName());
      System.out.print(" Last Name: " + employee.getLastName());
      System.out.println(" Salary: " + employee.getSalary());
      Map ec = employee.getCertificates();
      System.out.println("Certificate: " +
       (((Certificate)ec.get("ComputerScience")).getName()));
      System.out.println("Certificate: " +
       (((Certificate)ec.get("BusinessManagement")).getName()));
      System.out.println("Certificate: " +
       (((Certificate)ec.get("ProjectManagement")).getName()));
     }
     tx.commit();
   }catch (HibernateException e) {
     if (tx!=null) tx.rollback();
     e.printStackTrace();
   }finally {
     session.close();
   }
  }
  /* Method to update salary for an employee */
  public void updateEmployee(Integer EmployeeID, int salary ){
   Session session = factory.openSession();
   Transaction tx = null;
   try{
     tx = session.beginTransaction();
     Employee employee =
          (Employee)session.get(Employee.class, EmployeeID);
     employee.setSalary( salary );
     session.update(employee);
     tx.commit();
   }catch (HibernateException e) {
     if (tx!=null) tx.rollback();
     e.printStackTrace();
   }finally {
     session.close();
   }
  }
  /* Method to delete an employee from the records */
  public void deleteEmployee(Integer EmployeeID){
   Session session = factory.openSession();
   Transaction tx = null;
   try{
     tx = session.beginTransaction();
     Employee employee =
          (Employee)session.get(Employee.class, EmployeeID);
     session.delete(employee);
     tx.commit();
   }catch (HibernateException e) {
     if (tx!=null) tx.rollback();
     e.printStackTrace();
   }finally {
     session.close();
   }
  }
}

编译和执行:
下面是步骤来编译并运行上述应用程序。请确保您已在进行的编译和执行之前,已经适当地设置PATH和CLASSPATH。

  • 创建hibernate.cfg.xml配置文件,在配置章节已经解释和学习。
  • 创建Employee.hbm.xml映射文件,如上图所示。
  • 创建Employee.java源文件,如上图所示,并编译它。
  • 创建Certificate.java源文件,如上图所示,并编译它。
  • 创建ManageEmployee.java源文件,如上图所示,并编译它。
  • 执行ManageEmployee二进制文件来运行程序。

会在屏幕上获得以下结果,并同时创建记录在员工和证书表。

?
1
$java ManageEmployee

.......VARIOUS LOG MESSAGES WILL DISPLAY HERE........

?
1
2
3
4
5
6
7
8
First Name: Manoj Last Name: Kumar Salary: 4000
Certificate: MCA
Certificate: MBA
Certificate: PMP
First Name: Manoj Last Name: Kumar Salary: 5000
Certificate: MCA
Certificate: MBA
Certificate: PMP

如果检查员工和证书表,就应该记录下了:

?
1
mysql> select * from EMPLOYEE;
?
1
2
3
4
5
6
+----+------------+-----------+--------+
| id | first_name | last_name | salary |
+----+------------+-----------+--------+
| 60 | Manoj   | Kumar   |  5000 |
+----+------------+-----------+--------+
1 row in set (0.00 sec)
?
1
mysql>select * from CERTIFICATE;
?
1
2
3
4
5
6
7
8
+----+--------------------+------------------+-------------+
| id | certificate_type  | certificate_name | employee_id |
+----+--------------------+------------------+-------------+
| 16 | ProjectManagement | PMP       |     60 |
| 17 | BusinessManagement | MBA       |     60 |
| 18 | ComputerScience  | MCA       |     60 |
+----+--------------------+------------------+-------------+
3 rows in set (0.00 sec)


SortedMap映射
SortedMap是存储在键 - 值对的元素,并提供键的总体排序类似java的集合作为映射。重复的元素未在映射不允许的。该映射是根据其键的自然顺序进行排序,或者通过提供通常在有序映射的创建时间比较。

SortedMap被映射在该映射表中的<map>元素和有序映射可以在java.util.TreeMap中被初始化。
我们依然使用上面定义的RDBMS表和POJO类来讲下面的例子:
定义Hibernate映射文件:
让我们开发指示Hibernate如何定义的类映射到数据库表的映射文件。<map>元素将被用于定义所使用的映射的规则。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
 "-//Hibernate/Hibernate Mapping DTD//EN"
 "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
 
<hibernate-mapping>
  <class name="Employee" table="EMPLOYEE">
   <meta attribute="class-description">
     This class contains the employee detail.
   </meta>
   <id name="id" type="int" column="id">
     <generator class="native"/>
   </id>
   <map name="certificates" cascade="all" sort="MyClass">
     <key column="employee_id"/>
     <index column="certificate_type" type="string"/>
     <one-to-many class="Certificate"/>
   </map>
   <property name="firstName" column="first_name" type="string"/>
   <property name="lastName" column="last_name" type="string"/>
   <property name="salary" column="salary" type="int"/>
  </class>
 
  <class name="Certificate" table="CERTIFICATE">
   <meta attribute="class-description">
     This class contains the certificate records.
   </meta>
   <id name="id" type="int" column="id">
     <generator class="native"/>
   </id>
   <property name="name" column="certificate_name" type="string"/>
  </class>
 
</hibernate-mapping>

应该保存的映射文件中的格式<classname>.hbm.xml。我们保存映射文件Employee.hbm.xml。前面已经熟悉了大部分的映射细节,但让我们再次看看映射文件中的所有元素:

映射文档是具有<hibernate-mapping>对应于每一个类包含2个<class>元素的根元素的XML文档。

<class>元素被用于定义数据库表从一个Java类特定的映射。 Java类名指定使用class元素的name属性和使用表属性数据库表名指定。

<meta>元素是可选元素,可以用来创建类的描述。

<id>元素映射在类中的唯一ID属性到数据库表的主键。 id元素的name属性是指属性的类和column属性是指在数据库表中的列。 type属性保存了Hibernate映射类型,这种类型的映射将会从Java转换为SQL数据类型。

id元素内的<generator>元素被用来自动生成的主键值。将生成元素的class属性设置让Hibernate对应于identity,sequence或者hilo中的算法来创建主键根据底层数据库的支持能力。

<property>元素用于一个Java类的属性映射到数据库表中的列。元素的name属性是指属性的类和column属性是指在数据库表中的列。 type属性保存了Hibernate映射类型,这种类型的映射将会从Java转换为SQL数据类型。

<map>元素用于设置证书和Employee类之间的关系。我们使用cascade属性的<map>元素来告诉Hibernate来保存证书的对象,同时为Employee对象。 name属性被设置为在父类中的定义的SortedMap变量,在我们的情况下,它是证书。排序属性可以设置为自然有自然排序,也可以设置为自定义类实现为java.util.Comparator。我们已经使用了一个类MyClass,它实现为java.util.Comparator扭转证书类实现的排序顺序。

<index>元素用于表示键/值对映射的键部分。该键将使用一个字符串类型的存储在列certificate_type。

<key>元素是包含外键的父对象,即在证书表中的列。表EMPLOYEE。

<one-to-many>元素表示一个Employee对象涉及到很多证书的对象,并因此,证书对象必须有与Employee父有关联。可以根据需要使用<one-to-one>,<many-to-one>或<many-to-many>这个元素。

如果使用sort="natural",然后我们并不需要创建一个单独的类,因为证书类已经实现了Comparable接口和hibernate会使用compareTo在证书类定义为比较的SortedMap()方法。但是,我们使用的是在我们的映射文件自定义的比较器类MyClass,所以我们必须创建这个类的基础上我们的排序算法。让我们做降在映射上可用的按键排序。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.util.Comparator;
 
public class MyClass implements Comparator <String>{
  public int compare(String o1, String o2) {
   final int BEFORE = -1;
   final int AFTER = 1;
 
   /* To reverse the sorting order, multiple by -1 */
   if (o2 == null) {
     return BEFORE * -1;
   }
 
   Comparable thisCertificate = o1;
   Comparable thatCertificate = o2;
 
   if(thisCertificate == null) {
     return AFTER * 1;
   } else if(thatCertificate == null) {
     return BEFORE * -1;
   } else {
     return thisCertificate.compareTo(thatCertificate) * -1;
   }
  }
}

最后,我们将创建应用程序类的main()方法来运行应用程序。我们将使用这个应用程序,以节省一些员工的记录地连同的证书,然后我们将提交CRUD操作上面的记录。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
import java.util.*;
 
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
 
public class ManageEmployee {
  private static SessionFactory factory;
  public static void main(String[] args) {
   try{
     factory = new Configuration().configure().buildSessionFactory();
   }catch (Throwable ex) {
     System.err.println("Failed to create sessionFactory object." + ex);
     throw new ExceptionInInitializerError(ex);
   }
   ManageEmployee ME = new ManageEmployee();
   /* Let us have a set of certificates for the first employee */
   TreeMap set1 = new TreeMap();
   set1.put("ComputerScience", new Certificate("MCA"));
   set1.put("BusinessManagement", new Certificate("MBA"));
   set1.put("ProjectManagement", new Certificate("PMP"));
 
   /* Add employee records in the database */
   Integer empID1 = ME.addEmployee("Manoj", "Kumar", 4000, set1);
 
   /* Another set of certificates for the second employee */
   TreeMap set2 = new TreeMap();
   set2.put("ComputerScience", new Certificate("MCA"));
   set2.put("BusinessManagement", new Certificate("MBA"));
 
   /* Add another employee record in the database */
   Integer empID2 = ME.addEmployee("Dilip", "Kumar", 3000, set2);
 
   /* List down all the employees */
   ME.listEmployees();
 
   /* Update employee's salary records */
   ME.updateEmployee(empID1, 5000);
 
   /* Delete an employee from the database */
   ME.deleteEmployee(empID2);
 
   /* List down all the employees */
   ME.listEmployees();
 
  }
 
  /* Method to add an employee record in the database */
  public Integer addEmployee(String fname, String lname,
                   int salary, TreeMap cert){
   Session session = factory.openSession();
   Transaction tx = null;
   Integer employeeID = null;
   try{
     tx = session.beginTransaction();
     Employee employee = new Employee(fname, lname, salary);
     employee.setCertificates(cert);
     employeeID = (Integer) session.save(employee);
     tx.commit();
   }catch (HibernateException e) {
     if (tx!=null) tx.rollback();
     e.printStackTrace();
   }finally {
     session.close();
   }
   return employeeID;
  }
 
  /* Method to list all the employees detail */
  public void listEmployees( ){
   Session session = factory.openSession();
   Transaction tx = null;
   try{
     tx = session.beginTransaction();
     List employees = session.createQuery("FROM Employee").list();
     for (Iterator iterator1 =
              employees.iterator(); iterator1.hasNext();){
      Employee employee = (Employee) iterator1.next();
      System.out.print("First Name: " + employee.getFirstName());
      System.out.print(" Last Name: " + employee.getLastName());
      System.out.println(" Salary: " + employee.getSalary());
      SortedMap<String, Certificate> map =
                        employee.getCertificates();
      for(Map.Entry<String,Certificate> entry : map.entrySet()){
        System.out.print("\tCertificate Type: " + entry.getKey());
        System.out.println(", Name: " +
                    (entry.getValue()).getName());
      }
     }
     tx.commit();
   }catch (HibernateException e) {
     if (tx!=null) tx.rollback();
     e.printStackTrace();
   }finally {
     session.close();
   }
  }
  /* Method to update salary for an employee */
  public void updateEmployee(Integer EmployeeID, int salary ){
   Session session = factory.openSession();
   Transaction tx = null;
   try{
     tx = session.beginTransaction();
     Employee employee =
          (Employee)session.get(Employee.class, EmployeeID);
     employee.setSalary( salary );
     session.update(employee);
     tx.commit();
   }catch (HibernateException e) {
     if (tx!=null) tx.rollback();
     e.printStackTrace();
   }finally {
     session.close();
   }
  }
  /* Method to delete an employee from the records */
  public void deleteEmployee(Integer EmployeeID){
   Session session = factory.openSession();
   Transaction tx = null;
   try{
     tx = session.beginTransaction();
     Employee employee =
          (Employee)session.get(Employee.class, EmployeeID);
     session.delete(employee);
     tx.commit();
   }catch (HibernateException e) {
     if (tx!=null) tx.rollback();
     e.printStackTrace();
   }finally {
     session.close();
   }
  }
}

编译和执行: 
可以看到证书已排序顺序相反。可以通过改变映射文件试试,只需设置sort="natural"和执行程序,并比较结果。

?
1
$java ManageEmployee
?
1
2
3
4
5
6
7
8
9
10
11
12
13
.......VARIOUS LOG MESSAGES WILL DISPLAY HERE........
 
First Name: Manoj Last Name: Kumar Salary: 4000
 Certificate Type: ProjectManagement, Name: PMP
 Certificate Type: ComputerScience, Name: MCA
 Certificate Type: BusinessManagement, Name: MBA
First Name: Dilip Last Name: Kumar Salary: 3000
 Certificate Type: ComputerScience, Name: MCA
 Certificate Type: BusinessManagement, Name: MBA
First Name: Manoj Last Name: Kumar Salary: 5000
 Certificate Type: ProjectManagement, Name: PMP
 Certificate Type: ComputerScience, Name: MCA
 Certificate Type: BusinessManagement, Name: MBA

如果检查员工和证书表,就应该记录下了:

?
1
mysql> select * from EMPLOYEE;
?
1
2
3
4
5
6
+----+------------+-----------+--------+
| id | first_name | last_name | salary |
+----+------------+-----------+--------+
| 74 | Manoj   | Kumar   |  5000 |
+----+------------+-----------+--------+
1 row in set (0.00 sec)
?
1
mysql> select * from CERTIFICATE;
?
1
2
3
4
5
6
7
8
+----+--------------------+------------------+-------------+
| id | certificate_type  | certificate_name | employee_id |
+----+--------------------+------------------+-------------+
| 52 | BusinessManagement | MBA       |     74 |
| 53 | ComputerScience  | MCA       |     74 |
| 54 | ProjectManagement | PMP       |     74 |
+----+--------------------+------------------+-------------+
3 rows in set (0.00 sec)

延伸 · 阅读

精彩推荐