You are viewing a plain text version of this content. The canonical link for it is here.
Posted to submarine-dev@hadoop.apache.org by zt...@apache.org on 2019/09/27 08:55:48 UTC

[hadoop-submarine] branch master updated: [SUBMARINE-200] Submarine project manager ProjectRestApi

This is an automated email from the ASF dual-hosted git repository.

ztang pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hadoop-submarine.git


The following commit(s) were added to refs/heads/master by this push:
     new 6ab4dba  [SUBMARINE-200] Submarine project manager ProjectRestApi
6ab4dba is described below

commit 6ab4dbad432517a7215ed89d5f6cfa1a753cb377
Author: LinhaoZhu <zh...@163.com>
AuthorDate: Fri Sep 27 15:06:13 2019 +0800

    [SUBMARINE-200] Submarine project manager ProjectRestApi
    
    ### What is this PR for?
    1. org.apache.submarine.database.service.ProjectService
    2. ProjectMapper.xml, ProjectFilesMapper.xml
    3. org.apache.submarine.rest.ProjectRestApi
    4. database table: project, project_files;
    
    ### What type of PR is it?
    [ Feature ]
    
    ### Todos
    * [ ] - Task
    
    ### What is the Jira issue?
    * [SUBMARINE-200](https://issues.apache.org/jira/browse/SUBMARINE-200)
    
    ### How should this be tested?
    * [CI Pass](https://travis-ci.org/LinhaoZhu/hadoop-submarine/builds/590285482)
    
    ### Screenshots (if appropriate)
    
    ### Questions:
    * Does the licenses files need update? Yes/No
    * Is there breaking changes for older versions? Yes/No
    * Does this needs documentation? Yes/No
    
    Author: LinhaoZhu <zh...@163.com>
    
    Closes #17 from LinhaoZhu/SUBMARINE-200 and squashes the following commits:
    
    30edf0d [LinhaoZhu] Commit the restore to code unrelated to [SUBMARINE-200]
    b6e5c83 [LinhaoZhu]  Commit the restore to code unrelated to [SUBMARINE-200]
    e01ca1d [LinhaoZhu] [SUBMARINE-200] [SERVER] Submarine project manager ProjectRestApi
    badd744 [LinhaoZhu] [SUBMARINE-200] [SERVER] Submarine project manager ProjectRestApi
    7e23cf8 [LinhaoZhu] [SUBMARINE-200] [SERVER] Submarine project manager ProjectRestApi
    1065a89 [LinhaoZhu] [SUBMARINE-200] [SERVER] Submarine project manager ProjectRestApi
    5e2862d [LinhaoZhu] [SUBMARINE-200] [SERVER] Submarine project manager ProjectRestApi
    25a8201 [LinhaoZhu] Submarine project manager ProjectRestApi
---
 docs/database/submarine.sql                        |   4 +-
 .../apache/submarine/database/entity/Project.java  |  88 +++++++++++
 .../submarine/database/entity/ProjectFiles.java    |  46 ++++++
 .../database/mappers/ProjectFilesMapper.java       |  39 +++++
 .../submarine/database/mappers/ProjectMapper.java  |  36 +++++
 .../database/service/ProjectFilesService.java      |  46 ++++++
 .../submarine/database/service/ProjectService.java | 158 +++++++++++++++++++
 .../org/apache/submarine/rest/ProjectRestApi.java  |  70 +++++++++
 .../src/main/resources/mbgConfiguration.xml        |  35 +++--
 .../src/main/resources/mybatis-config.xml          |  29 ++--
 .../database/mappers/ProjectFilesMapper.xml        | 159 +++++++++++++++++++
 .../submarine/database/mappers/ProjectMapper.xml   | 170 ++++++++++++++++++++
 .../database/service/ProjectServiceTest.java       | 173 +++++++++++++++++++++
 13 files changed, 1020 insertions(+), 33 deletions(-)

diff --git a/docs/database/submarine.sql b/docs/database/submarine.sql
index b4194bd..a8b2482 100644
--- a/docs/database/submarine.sql
+++ b/docs/database/submarine.sql
@@ -159,6 +159,7 @@ CREATE TABLE `team_member` (
 -- ----------------------------
 -- Table structure for project
 -- ----------------------------
+DROP TABLE IF EXISTS `project`;
 CREATE TABLE `project` (
   `id` varchar(32) NOT NULL,
   `project_name` varchar(100) NOT NULL COMMENT 'project name',
@@ -173,11 +174,11 @@ CREATE TABLE `project` (
   PRIMARY KEY  (`id`)/*,
   CONSTRAINT `FK_PROJECT_USER_NAME` FOREIGN KEY (`user_name`) REFERENCES `sys_user` (`user_name`)*/
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-DROP TABLE IF EXISTS `project`;
 
 -- ----------------------------
 -- Table structure for project_files
 -- ----------------------------
+DROP TABLE IF EXISTS `project_files`;
 CREATE TABLE `project_files` (
   `id` varchar(32) NOT NULL,
   `project_id` varchar(32) NOT NULL COMMENT 'project id',
@@ -190,4 +191,3 @@ CREATE TABLE `project_files` (
   PRIMARY KEY  (`id`)/*,
   CONSTRAINT `FK_PROJECT_FILES_PRJ_ID` FOREIGN KEY (`project_id`) REFERENCES `project` (`id`)*/
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-DROP TABLE IF EXISTS `project_files`;
diff --git a/submarine-workbench/submarine-server/src/main/java/org/apache/submarine/database/entity/Project.java b/submarine-workbench/submarine-server/src/main/java/org/apache/submarine/database/entity/Project.java
new file mode 100644
index 0000000..9385fbd
--- /dev/null
+++ b/submarine-workbench/submarine-server/src/main/java/org/apache/submarine/database/entity/Project.java
@@ -0,0 +1,88 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License. See accompanying LICENSE file.
+ */
+package org.apache.submarine.database.entity;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class Project extends BaseEntity {
+  private String projectName;
+
+  // 0:Private, 1:Team, 2:Public
+  private Integer visibility;
+
+  // 0:notebook, 1:python, 2:spark, 3:R, 4:tensorflow, 5:pytorch
+  private Integer type;
+
+  private String description;
+
+  private String userName;
+
+  private List<ProjectFiles> projectFilesList;
+
+  public String getProjectName() {
+    return projectName;
+  }
+
+  public void setProjectName(String projectName) {
+    this.projectName = projectName == null ? null : projectName.trim();
+  }
+
+  public Integer getVisibility() {
+    return visibility;
+  }
+
+  public void setVisibility(Integer visibility) {
+    this.visibility = visibility;
+  }
+
+  public Integer getType() {
+    return type;
+  }
+
+  public void setType(Integer type) {
+    this.type = type;
+  }
+
+  public String getDescription() {
+    return description;
+  }
+
+  public void setDescription(String description) {
+    this.description = description == null ? null : description.trim();
+  }
+
+  public String getUserName() {
+    return userName;
+  }
+
+  public void setUserName(String userName) {
+    this.userName = userName == null ? null : userName.trim();
+  }
+
+  public List<ProjectFiles> getProjectFilesList() {
+    return projectFilesList;
+  }
+
+  public void setProjectFilesList(List<ProjectFiles> projectFilesList) {
+    this.projectFilesList = projectFilesList;
+  }
+
+  public void addProjectFilesList(ProjectFiles projectFiles) {
+    if (projectFilesList == null) {
+      projectFilesList = new ArrayList<>();
+    }
+    this.projectFilesList.add(projectFiles);
+  }
+}
diff --git a/submarine-workbench/submarine-server/src/main/java/org/apache/submarine/database/entity/ProjectFiles.java b/submarine-workbench/submarine-server/src/main/java/org/apache/submarine/database/entity/ProjectFiles.java
new file mode 100644
index 0000000..1243130
--- /dev/null
+++ b/submarine-workbench/submarine-server/src/main/java/org/apache/submarine/database/entity/ProjectFiles.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License. See accompanying LICENSE file.
+ */
+package org.apache.submarine.database.entity;
+
+public class ProjectFiles extends BaseEntity {
+  private String projectId;
+
+  private String fileName;
+
+  private String fileContent;
+
+  public String getProjectId() {
+    return projectId;
+  }
+
+  public void setProjectId(String projectId) {
+    this.projectId = projectId == null ? null : projectId.trim();
+  }
+
+  public String getFileName() {
+    return fileName;
+  }
+
+  public void setFileName(String fileName) {
+    this.fileName = fileName == null ? null : fileName.trim();
+  }
+
+  public String getFileContent() {
+    return fileContent;
+  }
+
+  public void setFileContent(String fileContent) {
+    this.fileContent = fileContent == null ? null : fileContent.trim();
+  }
+}
diff --git a/submarine-workbench/submarine-server/src/main/java/org/apache/submarine/database/mappers/ProjectFilesMapper.java b/submarine-workbench/submarine-server/src/main/java/org/apache/submarine/database/mappers/ProjectFilesMapper.java
new file mode 100644
index 0000000..18bad2e
--- /dev/null
+++ b/submarine-workbench/submarine-server/src/main/java/org/apache/submarine/database/mappers/ProjectFilesMapper.java
@@ -0,0 +1,39 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License. See accompanying LICENSE file.
+ */
+package org.apache.submarine.database.mappers;
+
+import org.apache.submarine.database.entity.ProjectFiles;
+
+import java.util.List;
+import java.util.Map;
+
+public interface ProjectFilesMapper {
+  List<ProjectFiles> selectAll(Map<String, Object> where);
+
+  int deleteByPrimaryKey(String id);
+
+  int deleteSelective(ProjectFiles record);
+
+  int insert(ProjectFiles record);
+
+  int insertSelective(ProjectFiles record);
+
+  ProjectFiles selectByPrimaryKey(String id);
+
+  int updateByPrimaryKeySelective(ProjectFiles record);
+
+  int updateByPrimaryKeyWithBLOBs(ProjectFiles record);
+
+  int updateByPrimaryKey(ProjectFiles record);
+}
diff --git a/submarine-workbench/submarine-server/src/main/java/org/apache/submarine/database/mappers/ProjectMapper.java b/submarine-workbench/submarine-server/src/main/java/org/apache/submarine/database/mappers/ProjectMapper.java
new file mode 100644
index 0000000..893d05f
--- /dev/null
+++ b/submarine-workbench/submarine-server/src/main/java/org/apache/submarine/database/mappers/ProjectMapper.java
@@ -0,0 +1,36 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License. See accompanying LICENSE file.
+ */
+package org.apache.submarine.database.mappers;
+
+import org.apache.ibatis.session.RowBounds;
+import org.apache.submarine.database.entity.Project;
+
+import java.util.List;
+import java.util.Map;
+
+public interface ProjectMapper {
+  List<Project> selectAll(Map<String, Object> where, RowBounds rowBounds);
+
+  int deleteByPrimaryKey(String id);
+
+  int insert(Project record);
+
+  int insertSelective(Project record);
+
+  Project selectByPrimaryKey(String id);
+
+  int updateByPrimaryKeySelective(Project record);
+
+  int updateByPrimaryKey(Project record);
+}
diff --git a/submarine-workbench/submarine-server/src/main/java/org/apache/submarine/database/service/ProjectFilesService.java b/submarine-workbench/submarine-server/src/main/java/org/apache/submarine/database/service/ProjectFilesService.java
new file mode 100644
index 0000000..f336740
--- /dev/null
+++ b/submarine-workbench/submarine-server/src/main/java/org/apache/submarine/database/service/ProjectFilesService.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License. See accompanying LICENSE file.
+ */
+package org.apache.submarine.database.service;
+
+import org.apache.ibatis.session.SqlSession;
+import org.apache.submarine.database.MyBatisUtil;
+import org.apache.submarine.database.entity.ProjectFiles;
+import org.apache.submarine.database.mappers.ProjectFilesMapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class ProjectFilesService {
+  private static final Logger LOG = LoggerFactory.getLogger(ProjectFilesService.class);
+
+  public List<ProjectFiles> queryList(String projectId) throws Exception {
+    LOG.info("queryPageList projectId:{}", projectId);
+
+    List<ProjectFiles> list = null;
+    try (SqlSession sqlSession = MyBatisUtil.getSqlSession()) {
+      ProjectFilesMapper projectFilesMapper = sqlSession.getMapper(ProjectFilesMapper.class);
+      Map<String, Object> where = new HashMap<>();
+      where.put("projectId", projectId);
+      list = projectFilesMapper.selectAll(where);
+    } catch (Exception e) {
+      LOG.error(e.getMessage(), e);
+      throw new Exception(e);
+    }
+    return list;
+  }
+
+}
diff --git a/submarine-workbench/submarine-server/src/main/java/org/apache/submarine/database/service/ProjectService.java b/submarine-workbench/submarine-server/src/main/java/org/apache/submarine/database/service/ProjectService.java
new file mode 100644
index 0000000..d1591c6
--- /dev/null
+++ b/submarine-workbench/submarine-server/src/main/java/org/apache/submarine/database/service/ProjectService.java
@@ -0,0 +1,158 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License. See accompanying LICENSE file.
+ */
+package org.apache.submarine.database.service;
+
+import org.apache.ibatis.session.RowBounds;
+import org.apache.ibatis.session.SqlSession;
+import org.apache.submarine.database.MyBatisUtil;
+import org.apache.submarine.database.entity.Project;
+import org.apache.submarine.database.entity.ProjectFiles;
+import org.apache.submarine.database.mappers.ProjectFilesMapper;
+import org.apache.submarine.database.mappers.ProjectMapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class ProjectService {
+  private static final Logger LOG = LoggerFactory.getLogger(ProjectService.class);
+
+  public List<Project> queryPageList(String userName,
+                                     String column,
+                                     String order,
+                                     int pageNo,
+                                     int pageSize) throws Exception {
+    LOG.info("queryPageList owner:{}, column:{}, order:{}, pageNo:{}, pageSize:{}",
+        userName, column, order, pageNo, pageSize);
+
+    List<Project> list = null;
+    try (SqlSession sqlSession = MyBatisUtil.getSqlSession()) {
+      ProjectMapper projectMapper = sqlSession.getMapper(ProjectMapper.class);
+      Map<String, Object> where = new HashMap<>();
+      where.put("userName", userName);
+      where.put("column", column);
+      where.put("order", order);
+      list = projectMapper.selectAll(where, new RowBounds(pageNo, pageSize));
+
+      ProjectFilesMapper projectFilesMapper = sqlSession.getMapper(ProjectFilesMapper.class);
+      // Query from project_files table, And set to Project Object
+      for (Project project : list) {
+        Map<String, Object> whereMember = new HashMap<>();
+        whereMember.put("projectId", project.getId());
+        List<ProjectFiles> projectFilesList = projectFilesMapper.selectAll(whereMember);
+        for (ProjectFiles projectFiles : projectFilesList) {
+          project.addProjectFilesList(projectFiles);
+        }
+      }
+    } catch (Exception e) {
+      LOG.error(e.getMessage(), e);
+      throw new Exception(e);
+    }
+    return list;
+  }
+
+  public boolean add(Project project) throws Exception {
+    LOG.info("add({})", project.toString());
+
+    try (SqlSession sqlSession = MyBatisUtil.getSqlSession()) {
+      ProjectMapper projectMapper = sqlSession.getMapper(ProjectMapper.class);
+      projectMapper.insert(project);
+
+      ProjectFilesMapper projectFilesMapper = sqlSession.getMapper(ProjectFilesMapper.class);
+      // add ProjectFiles, when add project, should insert 'ProjectFilesList' to ProjectFiles
+      List<ProjectFiles> list = project.getProjectFilesList();
+      for (ProjectFiles projectFiles : list) {
+        // ProjectId needs to be obtained after the Project is inserted into the database
+        projectFiles.setProjectId(project.getId());
+        projectFilesMapper.insert(projectFiles);
+      }
+
+      sqlSession.commit();
+    } catch (Exception e) {
+      LOG.error(e.getMessage(), e);
+      throw new Exception(e);
+    }
+    return true;
+  }
+
+  public boolean updateByPrimaryKeySelective(Project project) throws Exception {
+    LOG.info("updateByPrimaryKeySelective({})", project.toString());
+    try (SqlSession sqlSession = MyBatisUtil.getSqlSession()) {
+      ProjectMapper projectMapper = sqlSession.getMapper(ProjectMapper.class);
+      projectMapper.updateByPrimaryKeySelective(project);
+
+      ProjectFilesMapper projectFilesMapper = sqlSession.getMapper(ProjectFilesMapper.class);
+      Map<String, Object> where = new HashMap<>();
+      where.put("projectId", project.getId());
+      // Take two lists of difference
+      List<ProjectFiles> oldProjectFiles = projectFilesMapper.selectAll(where);
+      List<String> oldProjectFilesId = new ArrayList<>();
+      for (ProjectFiles oldProjectFile : oldProjectFiles) {
+        oldProjectFilesId.add(oldProjectFile.getId());
+      }
+      List<ProjectFiles> currProjectFiles = project.getProjectFilesList();
+      List<String> currProjectFilesId = new ArrayList<>();
+      for (ProjectFiles currProjectFile : currProjectFiles) {
+        currProjectFilesId.add(currProjectFile.getId());
+      }
+
+      for (ProjectFiles old : oldProjectFiles) {
+        if (!currProjectFilesId.contains(old.getId())) {
+          projectFilesMapper.deleteByPrimaryKey(old.getId());
+        } else {
+          for (ProjectFiles currProjectFile : currProjectFiles) {
+            if (currProjectFile.getId() != null && currProjectFile.getId().equals(old.getId())) {
+              projectFilesMapper.updateByPrimaryKeySelective(currProjectFile);
+            }
+          }
+        }
+      }
+      for (ProjectFiles curr : currProjectFiles) {
+        if (curr.getId() == null) {
+          // TODO(zhulinhao):The front desk should pass the projectId
+          curr.setProjectId(project.getId());
+          projectFilesMapper.insert(curr);
+        }
+      }
+
+      sqlSession.commit();
+    } catch (Exception e) {
+      LOG.error(e.getMessage(), e);
+      throw new Exception(e);
+    }
+    return true;
+  }
+
+  public boolean delete(String id) throws Exception {
+    LOG.info("delete({})", id);
+    try (SqlSession sqlSession = MyBatisUtil.getSqlSession()) {
+      ProjectMapper projectMapper = sqlSession.getMapper(ProjectMapper.class);
+      projectMapper.deleteByPrimaryKey(id);
+
+      ProjectFilesMapper projectFilesMapper = sqlSession.getMapper(ProjectFilesMapper.class);
+      ProjectFiles projectFiles = new ProjectFiles();
+      projectFiles.setProjectId(id);
+      projectFilesMapper.deleteSelective(projectFiles);
+      sqlSession.commit();
+    } catch (Exception e) {
+      LOG.error(e.getMessage(), e);
+      throw new Exception(e);
+    }
+    return true;
+  }
+
+}
diff --git a/submarine-workbench/submarine-server/src/main/java/org/apache/submarine/rest/ProjectRestApi.java b/submarine-workbench/submarine-server/src/main/java/org/apache/submarine/rest/ProjectRestApi.java
new file mode 100644
index 0000000..9f9ae67
--- /dev/null
+++ b/submarine-workbench/submarine-server/src/main/java/org/apache/submarine/rest/ProjectRestApi.java
@@ -0,0 +1,70 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License. See accompanying LICENSE file.
+ */
+package org.apache.submarine.rest;
+
+import com.github.pagehelper.PageInfo;
+import org.apache.submarine.annotation.SubmarineApi;
+import org.apache.submarine.database.entity.Project;
+import org.apache.submarine.database.service.ProjectService;
+import org.apache.submarine.server.JsonResponse;
+import org.apache.submarine.server.JsonResponse.ListResult;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Response;
+import java.util.ArrayList;
+import java.util.List;
+
+@Path("/project")
+@Produces("application/json")
+@Singleton
+public class ProjectRestApi {
+  private static final Logger LOG = LoggerFactory.getLogger(ProjectRestApi.class);
+
+  private ProjectService projectService = new ProjectService();
+
+  @Inject
+  public ProjectRestApi() {
+  }
+
+  @GET
+  @Path("/list")
+  @SubmarineApi
+  public Response list(@QueryParam("userName") String userName,
+                       @QueryParam("column") String column,
+                       @QueryParam("order") String order,
+                       @QueryParam("pageNo") int pageNo,
+                       @QueryParam("pageSize") int pageSize) {
+    LOG.info("ProjectRestApi.list() owner:{}, pageNo:{}, pageSize:{}", userName, pageNo, pageSize);
+
+    List<Project> projectList = new ArrayList<>();
+    try {
+      projectList = projectService.queryPageList("liuxun", column, order, pageNo, pageSize);
+    } catch (Exception e) {
+      LOG.error(e.getMessage(), e);
+      return new JsonResponse.Builder<>(Response.Status.OK).success(false).build();
+    }
+    PageInfo<Project> page = new PageInfo<>(projectList);
+    ListResult<Project> listResult = new ListResult(projectList, page.getTotal());
+    return new JsonResponse.Builder<ListResult<Project>>(Response.Status.OK)
+        .success(true).result(listResult).build();
+  }
+
+}
diff --git a/submarine-workbench/submarine-server/src/main/resources/mbgConfiguration.xml b/submarine-workbench/submarine-server/src/main/resources/mbgConfiguration.xml
index 604c01f..d890e6f 100644
--- a/submarine-workbench/submarine-server/src/main/resources/mbgConfiguration.xml
+++ b/submarine-workbench/submarine-server/src/main/resources/mbgConfiguration.xml
@@ -1,20 +1,17 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!--
-  ~ Licensed to the Apache Software Foundation (ASF) under one or more
-  ~ contributor license agreements.  See the NOTICE file distributed with
-  ~ this work for additional information regarding copyright ownership.
-  ~ The ASF licenses this file to You under the Apache License, Version 2.0
-  ~ (the "License"); you may not use this file except in compliance with
-  ~ the License.  You may obtain a copy of the License at
-  ~
-  ~    http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License. See accompanying LICENSE file.
+-->
 <!DOCTYPE generatorConfiguration
     PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
     "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
@@ -54,7 +51,13 @@
     </javaClientGenerator>
 
     <!-- List all the table to generate the code, configuration is not generate Example files here -->
-    <table tableName="team_member" domainObjectName="TeamMember"
+    <table tableName="project" domainObjectName="Project"
+           enableCountByExample="false" enableUpdateByExample="false" enableDeleteByExample="false"
+           enableSelectByExample="false" selectByExampleQueryId="false">
+      <property name="useActualColumnNames" value="false"/>
+    </table>
+
+    <table tableName="project_files" domainObjectName="ProjectFiles"
            enableCountByExample="false" enableUpdateByExample="false" enableDeleteByExample="false"
            enableSelectByExample="false" selectByExampleQueryId="false">
       <property name="useActualColumnNames" value="false"/>
diff --git a/submarine-workbench/submarine-server/src/main/resources/mybatis-config.xml b/submarine-workbench/submarine-server/src/main/resources/mybatis-config.xml
index 2264910..4f1b3fa 100755
--- a/submarine-workbench/submarine-server/src/main/resources/mybatis-config.xml
+++ b/submarine-workbench/submarine-server/src/main/resources/mybatis-config.xml
@@ -1,20 +1,17 @@
 <?xml version='1.0' encoding='UTF-8' ?>
 <!--
-  ~ Licensed to the Apache Software Foundation (ASF) under one or more
-  ~ contributor license agreements.  See the NOTICE file distributed with
-  ~ this work for additional information regarding copyright ownership.
-  ~ The ASF licenses this file to You under the Apache License, Version 2.0
-  ~ (the "License"); you may not use this file except in compliance with
-  ~ the License.  You may obtain a copy of the License at
-  ~
-  ~    http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License. See accompanying LICENSE file.
+-->
 <!DOCTYPE configuration PUBLIC '-//mybatis.org//DTD Config 3.0//EN'
     'http://mybatis.org/dtd/mybatis-3-config.dtd'>
 <configuration>
@@ -57,5 +54,7 @@
     <mapper resource='org/apache/submarine/database/mappers/SysDeptMapper.xml'/>
     <mapper resource='org/apache/submarine/database/mappers/TeamMapper.xml'/>
     <mapper resource='org/apache/submarine/database/mappers/TeamMemberMapper.xml'/>
+    <mapper resource='org/apache/submarine/database/mappers/ProjectMapper.xml'/>
+    <mapper resource='org/apache/submarine/database/mappers/ProjectFilesMapper.xml'/>
   </mappers>
 </configuration>
diff --git a/submarine-workbench/submarine-server/src/main/resources/org/apache/submarine/database/mappers/ProjectFilesMapper.xml b/submarine-workbench/submarine-server/src/main/resources/org/apache/submarine/database/mappers/ProjectFilesMapper.xml
new file mode 100644
index 0000000..f37c24d
--- /dev/null
+++ b/submarine-workbench/submarine-server/src/main/resources/org/apache/submarine/database/mappers/ProjectFilesMapper.xml
@@ -0,0 +1,159 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License. See accompanying LICENSE file.
+-->
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.apache.submarine.database.mappers.ProjectFilesMapper">
+  <resultMap id="BaseEntityResultMap" type="org.apache.submarine.database.entity.BaseEntity">
+    <id property="id" column="id"/>
+    <result column="create_by" property="createBy"/>
+    <result column="create_time" property="createTime"/>
+    <result column="update_by" property="updateBy"/>
+    <result column="update_time" property="updateTime"/>
+  </resultMap>
+
+  <resultMap id="resultMap" type="org.apache.submarine.database.entity.ProjectFiles" extends="BaseEntityResultMap">
+    <result column="project_id" jdbcType="VARCHAR" property="projectId" />
+    <result column="file_name" jdbcType="VARCHAR" property="fileName" />
+    <result column="file_content" jdbcType="LONGVARCHAR" property="fileContent" />
+  </resultMap>
+
+  <sql id="Base_Column_List">
+    id, project_id, file_name, file_content, create_by, create_time, update_by, update_time
+  </sql>
+
+  <select id="selectAll" parameterType="java.util.Map" resultMap="resultMap">
+    SELECT
+    <include refid="Base_Column_List"/>
+    FROM project_files
+    WHERE 1 = 1
+    <if test="projectId != null">
+      AND project_id = #{projectId,jdbcType=VARCHAR}
+    </if>
+  </select>
+
+  <select id="selectByPrimaryKey" parameterType="java.lang.String" resultMap="resultMap">
+    select 
+    <include refid="Base_Column_List" />
+    from project_files
+    where id = #{id,jdbcType=VARCHAR}
+  </select>
+
+  <delete id="deleteByPrimaryKey" parameterType="java.lang.String">
+    delete from project_files
+    where id = #{id,jdbcType=VARCHAR}
+  </delete>
+
+  <delete id="deleteSelective" parameterType="org.apache.submarine.database.entity.ProjectFiles">
+    delete from project_files
+    where 1 = 1
+    <if test="projectId != null">
+      AND project_id = #{projectId,jdbcType=VARCHAR}
+    </if>
+  </delete>
+
+  <insert id="insert" parameterType="org.apache.submarine.database.entity.ProjectFiles">
+    <selectKey keyProperty="id" resultType="java.lang.String" order="BEFORE">
+      SELECT REPLACE(UUID(),"-","")
+    </selectKey>
+    insert into project_files (id, project_id, file_name, 
+      create_by, create_time, update_time, file_content)
+    values (#{id,jdbcType=VARCHAR}, #{projectId,jdbcType=VARCHAR}, #{fileName,jdbcType=VARCHAR}, 
+      #{createBy,jdbcType=VARCHAR}, now(), now(), #{fileContent,jdbcType=LONGVARCHAR})
+  </insert>
+
+  <insert id="insertSelective" parameterType="org.apache.submarine.database.entity.ProjectFiles">
+    insert into project_files
+    <trim prefix="(" suffix=")" suffixOverrides=",">
+      <if test="id != null">
+        id,
+      </if>
+      <if test="projectId != null">
+        project_id,
+      </if>
+      <if test="fileName != null">
+        file_name,
+      </if>
+      <if test="createBy != null">
+        create_by,
+      </if>
+      create_time, update_time,
+      <if test="fileContent != null">
+        file_content,
+      </if>
+    </trim>
+    <trim prefix="values (" suffix=")" suffixOverrides=",">
+      <if test="id != null">
+        #{id,jdbcType=VARCHAR},
+      </if>
+      <if test="projectId != null">
+        #{projectId,jdbcType=VARCHAR},
+      </if>
+      <if test="fileName != null">
+        #{fileName,jdbcType=VARCHAR},
+      </if>
+      <if test="createBy != null">
+        #{createBy,jdbcType=VARCHAR},
+      </if>
+      now(), now(),
+      <if test="fileContent != null">
+        #{fileContent,jdbcType=LONGVARCHAR},
+      </if>
+    </trim>
+  </insert>
+
+  <update id="updateByPrimaryKeySelective" parameterType="org.apache.submarine.database.entity.ProjectFiles">
+    update project_files
+    <set>
+      <if test="projectId != null">
+        project_id = #{projectId,jdbcType=VARCHAR},
+      </if>
+      <if test="fileName != null">
+        file_name = #{fileName,jdbcType=VARCHAR},
+      </if>
+      <if test="updateBy != null">
+        update_by = #{updateBy,jdbcType=VARCHAR},
+      </if>
+      <if test="updateTime != null">
+        update_time = #{updateTime,jdbcType=TIMESTAMP},
+      </if>
+      <if test="fileContent != null">
+        file_content = #{fileContent,jdbcType=LONGVARCHAR},
+      </if>
+    </set>
+    where id = #{id,jdbcType=VARCHAR}
+  </update>
+
+  <update id="updateByPrimaryKeyWithBLOBs" parameterType="org.apache.submarine.database.entity.ProjectFiles">
+    update project_files
+    set project_id = #{projectId,jdbcType=VARCHAR},
+      file_name = #{fileName,jdbcType=VARCHAR},
+      create_by = #{createBy,jdbcType=VARCHAR},
+      create_time = #{createTime,jdbcType=TIMESTAMP},
+      update_by = #{updateBy,jdbcType=VARCHAR},
+      update_time = #{updateTime,jdbcType=TIMESTAMP},
+      file_content = #{fileContent,jdbcType=LONGVARCHAR}
+    where id = #{id,jdbcType=VARCHAR}
+  </update>
+
+  <update id="updateByPrimaryKey" parameterType="org.apache.submarine.database.entity.ProjectFiles">
+    update project_files
+    set project_id = #{projectId,jdbcType=VARCHAR},
+      file_name = #{fileName,jdbcType=VARCHAR},
+      create_by = #{createBy,jdbcType=VARCHAR},
+      create_time = #{createTime,jdbcType=TIMESTAMP},
+      update_by = #{updateBy,jdbcType=VARCHAR},
+      update_time = #{updateTime,jdbcType=TIMESTAMP}
+    where id = #{id,jdbcType=VARCHAR}
+  </update>
+</mapper>
diff --git a/submarine-workbench/submarine-server/src/main/resources/org/apache/submarine/database/mappers/ProjectMapper.xml b/submarine-workbench/submarine-server/src/main/resources/org/apache/submarine/database/mappers/ProjectMapper.xml
new file mode 100644
index 0000000..7d440e3
--- /dev/null
+++ b/submarine-workbench/submarine-server/src/main/resources/org/apache/submarine/database/mappers/ProjectMapper.xml
@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License. See accompanying LICENSE file.
+-->
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.apache.submarine.database.mappers.ProjectMapper">
+  <resultMap id="BaseEntityResultMap" type="org.apache.submarine.database.entity.BaseEntity">
+    <id property="id" column="id"/>
+    <result column="create_by" property="createBy"/>
+    <result column="create_time" property="createTime"/>
+    <result column="update_by" property="updateBy"/>
+    <result column="update_time" property="updateTime"/>
+  </resultMap>
+
+  <resultMap id="resultMap" type="org.apache.submarine.database.entity.Project" extends="BaseEntityResultMap">
+    <result column="project_name" jdbcType="VARCHAR" property="projectName" />
+    <result column="visibility" jdbcType="INTEGER" property="visibility" />
+    <result column="type" jdbcType="INTEGER" property="type" />
+    <result column="description" jdbcType="VARCHAR" property="description" />
+    <result column="user_name" jdbcType="VARCHAR" property="userName" />
+  </resultMap>
+
+  <sql id="Base_Column_List">
+    id, project_name, visibility, type, description, user_name, create_by, create_time, 
+    update_by, update_time
+  </sql>
+
+  <select id="selectAll" parameterType="java.util.Map" resultMap="resultMap">
+    SELECT
+    <include refid="Base_Column_List"/>
+    FROM project
+    WHERE 1 = 1
+    <if test="userName!=null and userName!=''">AND `user_name` = #{userName}</if>
+    ORDER BY #{column} #{order}
+  </select>
+
+  <select id="selectByPrimaryKey" parameterType="java.lang.String" resultMap="resultMap">
+    select 
+    <include refid="Base_Column_List" />
+    from project
+    where id = #{id,jdbcType=VARCHAR}
+  </select>
+
+  <delete id="deleteByPrimaryKey" parameterType="java.lang.String">
+    delete from project
+    where id = #{id,jdbcType=VARCHAR}
+  </delete>
+
+  <insert id="insert" parameterType="org.apache.submarine.database.entity.Project">
+    <selectKey keyProperty="id" resultType="java.lang.String" order="BEFORE">
+      SELECT REPLACE(UUID(),"-","")
+    </selectKey>
+    insert into project (id, project_name, visibility, 
+      type, description, user_name, 
+      create_by, create_time, update_time)
+    values (#{id,jdbcType=VARCHAR}, #{projectName,jdbcType=VARCHAR}, #{visibility,jdbcType=INTEGER}, 
+      #{type,jdbcType=INTEGER}, #{description,jdbcType=VARCHAR}, #{userName,jdbcType=VARCHAR}, 
+      #{createBy,jdbcType=VARCHAR}, now(), now())
+  </insert>
+
+  <insert id="insertSelective" parameterType="org.apache.submarine.database.entity.Project">
+    insert into project
+    <trim prefix="(" suffix=")" suffixOverrides=",">
+      <if test="id != null">
+        id,
+      </if>
+      <if test="projectName != null">
+        project_name,
+      </if>
+      <if test="visibility != null">
+        visibility,
+      </if>
+      <if test="type != null">
+        type,
+      </if>
+      <if test="description != null">
+        description,
+      </if>
+      <if test="userName != null">
+        user_name,
+      </if>
+      <if test="createBy != null">
+        create_by,
+      </if>
+      <if test="createTime != null">
+        create_time,
+      </if>
+      <if test="updateTime != null">
+        update_time,
+      </if>
+    </trim>
+    <trim prefix="values (" suffix=")" suffixOverrides=",">
+      <if test="id != null">
+        #{id,jdbcType=VARCHAR},
+      </if>
+      <if test="projectName != null">
+        #{projectName,jdbcType=VARCHAR},
+      </if>
+      <if test="visibility != null">
+        #{visibility,jdbcType=INTEGER},
+      </if>
+      <if test="type != null">
+        #{type,jdbcType=INTEGER},
+      </if>
+      <if test="description != null">
+        #{description,jdbcType=VARCHAR},
+      </if>
+      <if test="userName != null">
+        #{userName,jdbcType=VARCHAR},
+      </if>
+      <if test="createBy != null">
+        #{createBy,jdbcType=VARCHAR},
+      </if>
+      <if test="createTime != null">
+        #{createTime,jdbcType=TIMESTAMP},
+      </if>
+      <if test="updateTime != null">
+        #{updateTime,jdbcType=TIMESTAMP},
+      </if>
+    </trim>
+  </insert>
+
+  <update id="updateByPrimaryKeySelective" parameterType="org.apache.submarine.database.entity.Project">
+    update project
+    <set>
+      <if test="projectName != null">
+        project_name = #{projectName,jdbcType=VARCHAR},
+      </if>
+      <if test="visibility != null">
+        visibility = #{visibility,jdbcType=INTEGER},
+      </if>
+      <if test="type != null">
+        type = #{type,jdbcType=INTEGER},
+      </if>
+      <if test="description != null">
+        description = #{description,jdbcType=VARCHAR},
+      </if>
+      <if test="userName != null">
+        user_name = #{userName,jdbcType=VARCHAR},
+      </if>
+      <if test="updateBy != null">
+        update_by = #{updateBy,jdbcType=VARCHAR},
+      </if>
+      update_time = now()
+    </set>
+    where id = #{id,jdbcType=VARCHAR}
+  </update>
+
+  <update id="updateByPrimaryKey" parameterType="org.apache.submarine.database.entity.Project">
+    update project
+    set project_name = #{projectName,jdbcType=VARCHAR},
+      visibility = #{visibility,jdbcType=INTEGER},
+      type = #{type,jdbcType=INTEGER},
+      description = #{description,jdbcType=VARCHAR},
+      user_name = #{userName,jdbcType=VARCHAR},
+      update_by = #{updateBy,jdbcType=VARCHAR},
+      update_time = #{updateTime,jdbcType=TIMESTAMP}
+    where id = #{id,jdbcType=VARCHAR}
+  </update>
+</mapper>
diff --git a/submarine-workbench/submarine-server/src/test/java/org/apache/submarine/database/service/ProjectServiceTest.java b/submarine-workbench/submarine-server/src/test/java/org/apache/submarine/database/service/ProjectServiceTest.java
new file mode 100644
index 0000000..9b03862
--- /dev/null
+++ b/submarine-workbench/submarine-server/src/test/java/org/apache/submarine/database/service/ProjectServiceTest.java
@@ -0,0 +1,173 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License. See accompanying LICENSE file.
+ */
+package org.apache.submarine.database.service;
+
+import org.apache.submarine.database.entity.Project;
+import org.apache.submarine.database.entity.ProjectFiles;
+import org.junit.After;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static junit.framework.TestCase.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class ProjectServiceTest {
+  private static final Logger LOG = LoggerFactory.getLogger(TeamServiceTest.class);
+  ProjectService projectService = new ProjectService();
+
+  @After
+  public void removeAllRecord() throws Exception {
+    List<Project> projectList = projectService.queryPageList(null, "create_time", "desc", 0, 100);
+    LOG.info("projectList.size():{}", projectList.size());
+    for (Project project : projectList) {
+      projectService.delete(project.getId());
+    }
+  }
+
+  @Test
+  public void queryPageList() throws Exception {
+    ProjectFiles projectFiles = new ProjectFiles();
+    projectFiles.setFileContent("ProjectServiceTest-FileContent");
+    projectFiles.setFileName("ProjectServiceTest-FileName");
+    projectFiles.setCreateBy("ProjectServiceTest-UserName");
+
+    Project project = new Project();
+    project.setDescription("ProjectServiceTest-Description");
+    project.setProjectName("ProjectServiceTest-ProjectName");
+    project.setType(1);
+    project.setUserName("ProjectServiceTest-UserName");
+    project.setVisibility(1);
+    project.setCreateBy("ProjectServiceTest-UserName");
+    List list = new ArrayList<ProjectFiles>();
+    list.add(projectFiles);
+    project.setProjectFilesList(list);
+
+    Boolean ret = projectService.add(project);
+    assertTrue(ret);
+
+    List<Project> projectList = projectService.queryPageList("ProjectServiceTest-UserName",
+        "create_time", "desc", 0, 100);
+    assertEquals(projectList.size(), 1);
+
+    Project projectDb = projectList.get(0);
+    assertEquals(project.getDescription(), projectDb.getDescription());
+    assertEquals(project.getProjectName(), projectDb.getProjectName());
+    assertEquals(project.getType(), projectDb.getType());
+    assertEquals(project.getUserName(), projectDb.getUserName());
+    assertEquals(project.getVisibility(), projectDb.getVisibility());
+    assertEquals(project.getCreateBy(), projectDb.getCreateBy());
+
+    assertEquals(projectDb.getProjectFilesList().size(), 1);
+
+    ProjectFiles projectFilesDb = projectDb.getProjectFilesList().get(0);
+    assertEquals(project.getId(), projectFilesDb.getProjectId());
+    assertEquals(projectFiles.getFileContent(), projectFilesDb.getFileContent());
+    assertEquals(projectFiles.getFileName(), projectFilesDb.getFileName());
+    assertEquals(projectFiles.getCreateBy(), projectFilesDb.getCreateBy());
+  }
+
+  @Test
+  public void updateByPrimaryKeySelective() throws Exception {
+    ProjectFiles projectFiles = new ProjectFiles();
+    projectFiles.setFileContent("ProjectServiceTest-FileContent");
+    projectFiles.setFileName("ProjectServiceTest-FileName");
+    projectFiles.setCreateBy("ProjectServiceTest-UserName");
+
+    Project project = new Project();
+    project.setDescription("ProjectServiceTest-Description");
+    project.setProjectName("ProjectServiceTest-ProjectName");
+    project.setType(1);
+    project.setUserName("ProjectServiceTest-UserName");
+    project.setVisibility(1);
+    project.setCreateBy("ProjectServiceTest-UserName");
+    List list = new ArrayList<ProjectFiles>();
+    list.add(projectFiles);
+    project.setProjectFilesList(list);
+
+    Boolean ret = projectService.add(project);
+    assertTrue(ret);
+
+    project.setProjectName("update_projectName");
+    project.setDescription("update_description");
+    project.setVisibility(2);
+    project.setUpdateBy("project_updateBy");
+    ProjectFiles projectFilesUpdate = new ProjectFiles();
+    projectFilesUpdate.setFileContent("ProjectServiceTest-FileContent2");
+    projectFilesUpdate.setFileName("ProjectServiceTest-FileName2");
+    projectFilesUpdate.setCreateBy("ProjectServiceTest-UserName2");
+    list.add(projectFilesUpdate);
+    projectFiles.setFileName("update_fileName");
+    projectFiles.setFileContent("update_fileContent");
+    projectFiles.setUpdateBy("projectFiles_updateby");
+    boolean editRet = projectService.updateByPrimaryKeySelective(project);
+    assertTrue(editRet);
+    List<Project> projectList = projectService.queryPageList("ProjectServiceTest-UserName",
+        "create_time", "desc", 0, 100);
+    assertEquals(projectList.size(), 1);
+
+    Project projectDb = projectList.get(0);
+    assertEquals(project.getProjectName(), projectDb.getProjectName());
+    assertEquals(project.getDescription(), projectDb.getDescription());
+    assertEquals(project.getVisibility(), projectDb.getVisibility());
+    assertEquals(project.getUpdateBy(), projectDb.getUpdateBy());
+    LOG.info("update_time:{}", projectDb.getUpdateTime());
+
+    List<ProjectFiles> projectFilesList = projectDb.getProjectFilesList();
+    for (ProjectFiles files : projectFilesList) {
+      if (!files.getFileContent().equals("ProjectServiceTest-FileContent2")) {
+        assertEquals(files.getFileName(), projectFiles.getFileName());
+        assertEquals(files.getFileContent(), projectFiles.getFileContent());
+        assertEquals(files.getUpdateBy(), projectFiles.getUpdateBy());
+      }
+    }
+    assertEquals(projectFilesList.size(), 2);
+  }
+
+  @Test
+  public void delete() throws Exception {
+    ProjectFiles projectFiles = new ProjectFiles();
+    projectFiles.setFileContent("ProjectServiceTest-FileContent");
+    projectFiles.setFileName("ProjectServiceTest-FileName");
+    projectFiles.setCreateBy("ProjectServiceTest-UserName");
+
+    Project project = new Project();
+    project.setDescription("ProjectServiceTest-Description");
+    project.setProjectName("ProjectServiceTest-ProjectName");
+    project.setType(1);
+    project.setUserName("ProjectServiceTest-UserName");
+    project.setVisibility(1);
+    project.setCreateBy("ProjectServiceTest-UserName");
+    List list = new ArrayList<ProjectFiles>();
+    list.add(projectFiles);
+    project.setProjectFilesList(list);
+
+    Boolean ret = projectService.add(project);
+    assertTrue(ret);
+
+    Boolean deleteRet = projectService.delete(project.getId());
+    assertTrue(deleteRet);
+
+    List<Project> projectList = projectService.queryPageList("ProjectServiceTest-UserName",
+        "create_time", "desc", 0, 100);
+    assertEquals(projectList.size(), 0);
+
+    ProjectFilesService projectFilesService = new ProjectFilesService();
+    List<ProjectFiles> projectFilesList = projectFilesService.queryList(project.getId());
+    assertEquals(projectFilesList.size(), 0);
+  }
+}