Springboot+Docker+Neo4j实现组织树

组织树是一个常见的数据结构,通常处理方法有用关系型数据库的二维表变相处理的,也有用LDAP数据库处理的,在这里就不过多解释,各有利弊。特别是通过关系型数据库变相处理树状数据结构从思维模式到编程进而到效率上都存在先天问题,如果遇到数十万或者更多的节点的时候处理起来就更麻烦,这个是因为树状数据结构和二维表数据结构的巨大差异造成的。那么针对树状数据结构就应该用树的算法解决,目前有很多成熟的基于图的数据库具备处理各种复杂结构图的优势,而树是一种最简单的图。今天就介绍开源界最为流行的图数据库Neo4j。

Spring 访问Neo4j整体架构

Springboot+Docker+Neo4j实现组织树_第1张图片
Neo4j架构

备注:Neo4j数据库提供3种协议对外提供访问接口:bolt、http、java嵌入客户端。

以docker方式部署Neo4j数据库

  1. 从docker镜像仓库拉去镜像,执行下面命令
docker pull neo4j
  1. 运行neo4j,执行如下命令
 docker run -d -p 7473:7473 -p 7687:7687 -p 7474:7474 neo4j

其中3个端口是通过不同协议对外提供访问的。

springboot开发相关功能

  1. 依赖包引入
    neo4j已经很好的支持springboot特性,只需要引入starter包即可,这里要特别说明的是,因为对接neo4j的协议是可选择的,所以要根据所选择的协议引入相应的驱动包(driver)。
    
        
        
            org.springframework.boot
            spring-boot-starter-data-neo4j
        
        
            org.neo4j
            neo4j-ogm-http-driver
        
    
  1. 配置数据库连接
    neo4j的数据库连接配置非常简单,和其他关系数据库配置方法类似,只需要把配置属性写入到application.yml中即可。
#neo4j配置
spring:
  data:
    neo4j:
      uri: http://172.16.15.239:7474
      username: neo4j
      password: syswinneo4j
Springboot+Docker+Neo4j实现组织树_第2张图片
工程结构
  1. 编写节点数据结构Bean
    这个Bean是neo4j节点数据结构,该数据结构包含三个部分:
    3.1 节点ID,这个属性是必须的,是节点在neo4j中唯一的标识;
    3.2 节点数据属性,这些部分是节点本身的一些属性定义,可以是java的所有可识别类型,包括复杂类型;
    3.3 子节点,这些子节点就是要和该数据节点产生关联关系的数据节点,可以是一种,也可以是多种,可以是一种关系,也可以是多种关系。
    3.4 典型组织树的4中节点数据结构:
    3.4.1 组织节点
//定义组织节点数据结构
@Data
@NodeEntity(label = "Organization")
public class Organization {
    @Id
    @GeneratedValue
    private Long id;
    private String name;

    @Relationship(type = "group", direction = Relationship.OUTGOING)
    private List groups=new ArrayList<>();

    public void addGroup(Group node) {
        this.groups.add(node);
    }

}

3.4.2 集团节点

//定义集团节点数据结构
@Data
@NodeEntity(label = "Group")
public class Group {
    @Id
    @GeneratedValue
    private Long id;
    private String name;

    @Relationship(type = "department", direction = Relationship.OUTGOING)
    private List departments=new ArrayList<>();

    public void addDepartment(Department node) {
        this.departments.add(node);
    }

}

3.4.3 部门节点

//定义部门节点数据结构
@Data
@NodeEntity(label = "Department")
public class Department {
    @Id
    @GeneratedValue
    private Long id;
    private String name;

    @Relationship(type = "department", direction = Relationship.OUTGOING)
    private List departments=new ArrayList<>();
    @Relationship(type = "employee", direction = Relationship.OUTGOING)
    private List employees=new ArrayList<>();

    public void addDepartment(Department node) {
        this.departments.add(node);
    }

    public void addEmployee(Employee node) {
        this.employees.add(node);
    }
}

3.4.4 员工节点

//定义员工节点数据结构
@Data
@NodeEntity(label = "Employee")
public class Employee {
    @Id
    @GeneratedValue
    private Long id;
    private String userId;
    private String name;
}
  1. 编写Repository
    repository是扩展了Neo4jRepository的接口,是对neo4j数据表进行操作的统一入口,Neo4jRepository接口已经定义了绝大多数的CURD操作,如果不满足需求,可以扩展功能。下面是对照上面定义的四种节点数据操作的repository接口定义。
    4.1 组织节点数据操作接口
@Repository
public interface OrganizationRepository extends Neo4jRepository {
    Organization findByName(@Param("name") String name);
}

4.2 集团节点数据操作接口

@Repository
public interface GroupRepository extends Neo4jRepository {
    Group findByName(@Param("name") String name);
}

4.3 部门节点数据操作接口

@Repository
public interface DepartmentRepository extends Neo4jRepository {
    Department findByName(@Param("name") String name);
}

4.4 员工节点数据操作接口

@Repository
public interface EmployeeRepository extends Neo4jRepository {
    Employee findByName(@Param("name") String name);
}
  1. 实现neo4j数据库逻辑的服务
    复杂的业务逻辑以服务的方式实现,通过组合调用上面定义的接口方法来完成。
@Service
@Slf4j
public class KernalService {
    @Autowired
    private OrganizationRepository organizationRepository;
    @Autowired
    private GroupRepository groupRepository;
    @Autowired
    private DepartmentRepository departmentRepository;
    @Autowired
    private EmployeeRepository employeeRepository;

    public Long createOrg(String name){
        //存储节点
        Organization organization=new Organization();
        organization.setName(name);
        organizationRepository.save(organization);
        return organization.getId();
    }

    public Long createGroup(Long id,String name){
        //存储节点
        Group group=new Group();
        group.setName(name);
        groupRepository.save(group);

        Optional organization=organizationRepository.findById(id);
        if(organization.get().getId()!=id){
            organization.get().addGroup(group);
            organizationRepository.save(organization.get());
            return group.getId();
        }
        return -1L;
    }

    public Long createDep(Long id,String name){
        //存储节点
        Department department=new Department();
        department.setName(name);
        departmentRepository.save(department);

        Optional group=groupRepository.findById(id);
        if(group.get().getId()!=id){
            group.get().addDepartment(department);
            groupRepository.save(group.get());
            return department.getId();
        }
        return -1L;
    }

    public Long createEmployee(Long id,String userId,String name){
        //存储节点
        Employee employee=new Employee();
        employee.setUserId(userId);
        employee.setName(name);
        employeeRepository.save(employee);

        Optional parentDepartment=departmentRepository.findById(id);
        if(parentDepartment.get().getId()!=id){
            parentDepartment.get().addEmployee(employee);
            departmentRepository.save(parentDepartment.get());
            return employee.getId();
        }
        return -1L;
    }
}
  1. 编写RestController以web的方式对外提供服务
@Slf4j
@RestController
public class Controller {
    @Autowired
    private KernalService kernalService;

    @PostMapping(value = "/createOrg")
    @ApiOperation(value = "创建组织", notes = "用户中台组织创建组织")
    @ApiImplicitParams({
            @ApiImplicitParam(paramType = "query", name = "orgName", value = "组织名称", dataType = "String")
    })
    public ResultData createOrg(@RequestParam String orgName) {
        ResultData result=new ResultData();
        Long orgId=kernalService.createOrg(orgName);
        if(orgId>0) {
            result.setRetCode(0);
            result.setRetMessage("创建组织成功!");
            JSONObject data = new JSONObject();
            data.put("orgId", orgId);
            data.put("orgName", orgName);
            result.setData(data);
        }
        else {
            result.setRetCode(-100);
            result.setRetMessage("创建组织失败!");
        }
        return result;
    }

    @PostMapping(value = "/createGroup")
    @ApiOperation(value = "创建集团", notes = "用户中台组织创建集团")
    @ApiImplicitParams({
            @ApiImplicitParam(paramType = "query", name = "id", value = "组织ID", dataType = "Long"),
            @ApiImplicitParam(paramType = "query", name = "groupName", value = "集团名称", dataType = "String")
    })
    public ResultData createGroup(@RequestParam Long id,@RequestParam String groupName) {
        ResultData result=new ResultData();
        Long groupId=kernalService.createGroup(id,groupName);
        if(groupId>0) {
            result.setRetCode(0);
            result.setRetMessage("创建集团成功!");
            JSONObject data = new JSONObject();
            data.put("groupId", groupId);
            data.put("groupName", groupName);
            result.setData(data);
        }
        else {
            result.setRetCode(-100);
            result.setRetMessage("创建集团失败!");
        }
        return result;
    }

    @PostMapping(value = "/createDep")
    @ApiOperation(value = "创建部门", notes = "用户中台组织创建部门")
    @ApiImplicitParams({
            @ApiImplicitParam(paramType = "query", name = "id", value = "组织/上级部门ID", dataType = "Long"),
            @ApiImplicitParam(paramType = "query", name = "depName", value = "部门名称", dataType = "String")
    })
    public ResultData createDepartment(@RequestParam Long id,@RequestParam String depName) {
        ResultData result=new ResultData();
        Long depId=kernalService.createDep(id,depName);
        if(depId>0) {
            result.setRetCode(0);
            result.setRetMessage("创建部门成功!");
            JSONObject data = new JSONObject();
            data.put("depId", depId);
            data.put("depName", depName);
            result.setData(data);
        }
        else {
            result.setRetCode(-100);
            result.setRetMessage("创建部门失败!");
        }
        return result;
    }

    @PostMapping(value = "/createEmployee")
    @ApiOperation(value = "创建员工", notes = "用户中台组织创建员工")
    @ApiImplicitParams({
            @ApiImplicitParam(paramType = "query", name = "id", value = "所属部门ID", dataType = "Long"),
            @ApiImplicitParam(paramType = "query", name = "userId", value = "用户ID", dataType = "String"),
            @ApiImplicitParam(paramType = "query", name = "userName", value = "员工名称", dataType = "String")
    })
    public ResultData createDepartment(@RequestParam Long id,@RequestParam String userId,@RequestParam String userName) {
        ResultData result=new ResultData();
        Long employeeId=kernalService.createEmployee(id,userId,userName);
        if(employeeId>0) {
            result.setRetCode(0);
            result.setRetMessage("创建员工成功!");
            JSONObject data = new JSONObject();
            data.put("employeeId", employeeId);
            data.put("userId", userId);
            data.put("userName", userName);
            result.setData(data);
        }
        else {
            result.setRetCode(-100);
            result.setRetMessage("创建员工失败!");
        }
        return result;
    }

    @PostMapping(value = "/register")
    @ApiOperation(value = "注册", notes = "用户中台组织用户注册")
    @ApiImplicitParams({
            @ApiImplicitParam(paramType = "query", name = "userId", value = "基础用户ID", dataType = "String"),
            @ApiImplicitParam(paramType = "query", name = "orgId", value = "组织用户ID", dataType = "String"),
            @ApiImplicitParam(paramType = "query", name = "departmentId", value = "部门ID", dataType = "String")
    })
    public ResultData register(@RequestParam String userId, @RequestParam String orgId, @RequestParam String departmentId) {
        ResultData result=new ResultData();
        result.setRetCode(0);
        result.setRetMessage("注册成功!");
        JSONObject data=new JSONObject();
        data.put("id", userId);
        data.put("orgId",orgId);
        data.put("departmentId", departmentId);
        result.setData(data);
        return result;
    }
}
  1. 测试验证功能及性能
    这里为了验证neo4j的性能,批量创建了40万个节点,40万个管理关系,在300s内全部执行完成,整体性能方面非常优秀。
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class ApplicationTests {
    static final int groupNumber=20;
    static final int departmentNumber=100;
    static final int employeeNumber=200;

    @Autowired
    private OrganizationRepository organizationRepository;
    @Autowired
    private GroupRepository groupRepository;
    @Autowired
    private DepartmentRepository departmentRepository;
    @Autowired
    private EmployeeRepository employeeRepository;

    @Test
    public void contextLoads() {
    }

    @Test
    public void createOrgTest(){
        //存储节点
        Organization organization=new Organization();
        organization.setName("控股");
        organizationRepository.save(organization);

        List groups=new ArrayList<>();
        for(int i=0;i departments=new ArrayList<>();
        for(int i=0;i employees=new ArrayList<>();
        for(int i=0;i{
            organization.addGroup(v);
        });
        organizationRepository.save(organization);
        log.info("组织创建集团关系完成!");

        int countOrganization=0;
        int countDepartment=0;
        int countEmployee=0;

        for (int i = 0; i < groupNumber; i++) {
            for (int j = 0; j < departmentNumber; j++) {
                groups.get(countOrganization).addDepartment(departments.get(countDepartment++));
            }
            groupRepository.save(groups.get(countOrganization++));
        }
        log.info("集团创建部门关系完成!");

        countDepartment=0;
        for (int i = 0; i < groupNumber; i++) {
            for (int j = 0; j < departmentNumber; j++) {
                for (int k = 0; k < employeeNumber; k++) {
                    departments.get(countDepartment).addEmployee(employees.get(countEmployee++));
                }
                departmentRepository.save(departments.get(countDepartment++));
            }
        }
        log.info("部门创建员工关系完成!");

    }

    @Test
    public void cleanDataTest(){
        organizationRepository.deleteAll();
        groupRepository.deleteAll();
        departmentRepository.deleteAll();
        employeeRepository.deleteAll();
    }

}
2019-11-26 15:42:36.444  INFO 98261 --- [           main] c.s.p.middle.user.org.ApplicationTests   : 组织创建集团关系完成!
2019-11-26 15:42:37.681  INFO 98261 --- [           main] c.s.p.middle.user.org.ApplicationTests   : 集团创建部门关系完成!
2019-11-26 15:45:24.000  INFO 98261 --- [           main] c.s.p.middle.user.org.ApplicationTests   : 部门创建员工关系完成!
Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 312.644 sec

图形化界面展示neo4j数据

neo4j本身提供了较为完善的图形化管理系统,方便对neo4j数据库进行管理和维护,相关功能不再赘述,可以通过浏览器打开。


neo4j图书化管理

你可能感兴趣的:(Springboot+Docker+Neo4j实现组织树)