zoukankan      html  css  js  c++  java
  • 从零一起学Spring Boot之LayIM项目长成记(三) 数据库的简单设计和JPA的简单使用。

    前言

      今天是第三篇了,上一篇简单模拟了数据,实现了LayIM页面的数据加载。那么今天呢就要用数据库的数据了。闲言少叙,书归正传,让我们开始吧。

    数据库

      之前有好多小伙伴问我数据库是怎么设计的。我个人用关系型数据库比较多,一般就是根据业务来分析,一对一的关系,一对多的关系,多对多的关系等,那么对于LayIM就根据这几个关系出发。而且先根据业务来设计。它初始化的数据我们都见过了,数据中分别包含以下四个部分。

    • 个人用户信息
    • 好友分组信息
    • 群组信息

      大部分业务都是围绕着用户转的,那么我们一条一条的分析。首先,个人用户信息不必说,这里我们就根据LayIM所需的用户字段设计就好,另外加上createAt字段或者其他字段都行。很简单,用户表SQL如下:

    CREATE TABLE `user` (
      `id` bigint(20) NOT NULL AUTO_INCREMENT,
      `avatar` varchar(255) DEFAULT NULL,
      `sign` varchar(255) DEFAULT NULL,
      `user_name` varchar(255) DEFAULT NULL,
      `create_at` bigint(20) DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=984389 DEFAULT CHARSET=utf8;

      好友分组信息,一个用户可以拥有多个分组,而每个分组下又可以有多个好友。 所以,维护好友关系需要两张表,分组表和用户(好友)分组关系表

    CREATE TABLE `friend_group` (
      `id` bigint(20) NOT NULL AUTO_INCREMENT,
      `create_at` bigint(20) DEFAULT NULL,
      `name` varchar(255) DEFAULT NULL,
      `uid` bigint(20) DEFAULT NULL,
      PRIMARY KEY (`id`),
      KEY `FKawe6k1o9mujam11lxiwcwbqpu` (`uid`),
      CONSTRAINT `FKawe6k1o9mujam11lxiwcwbqpu` FOREIGN KEY (`uid`) REFERENCES `user` (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=770369 DEFAULT CHARSET=utf8;

      群组信息,一个用户可以有多个群,一个群内有多个用户,他们是多对多的关系。所以维护这个关系需要两张表。群组表和群员关系表。

    CREATE TABLE `big_group` (
      `id` bigint(20) NOT NULL AUTO_INCREMENT,
      `create_at` bigint(20) DEFAULT NULL,
      `avatar` varchar(255) DEFAULT NULL,
      `create_uid` bigint(20) DEFAULT NULL,
      `description` varchar(255) DEFAULT NULL,
      `group_name` varchar(255) DEFAULT NULL,
      PRIMARY KEY (`id`),
      KEY `FKdw1jlswinwe6gas1tqk1trgia` (`create_uid`),
      CONSTRAINT `FKdw1jlswinwe6gas1tqk1trgia` FOREIGN KEY (`create_uid`) REFERENCES `user` (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

      剩下的两个表没有写,分别是 (分组,用户)关系表,(群组,用户)关系表。 没错,他们都是多对多的关系。你可以在张三的好友列表里,也可以在李四的好友列表。同理,你可以在多个群里。

    JPA-表的创建

      上面简单介绍了表的结构,虽然写出了SQL语句,但是呢在项目里我们并不会手动创建表。因为JPA会帮我们实现自动创建。如下图,我使用的是update,(即实体类变化等会重新修改表结构)

      

      下面就用户表写一个对应的类。(例)

    //加上@Entity注解
    @Entity
    public class UserInfo {
        //主键注解
        @Id
        @GeneratedValue
        @Column(nullable = false)
        private long id;
    
        //列 可以自定义列名,长度等,或者是否为空
        @Column(name = "user_name",length = 20)
        private String name;
    
    }

      运行程序,查看一下表,已经成功创建,没有问题。

      

      但是只是简单的表还不够,因为我们知道,表之间是有关系的,于是JPA中的关系注解就派上用场了。

    JPA-关系的映射

      之所以在上文中提到了一对多,多对多等词,其实就是为了在这里具体解释。JPA提供了关系注解。@OneToMany,@OneToOne,@ManyToOne,@ManyToMany等。下面我会根据LayIM所需要的关系一一讲解。

      首先,一个用户对应多个好友分组。而一个分组只属于某个(当前)用户。这里可以理解为用户与好友分组的关系是一对多,即 @OneToMany。代码中是这样体现的。

        //一对多,一个用户有多个好友分组
        @OneToMany(mappedBy = "user")
        private List<FriendGroup> friendGroups;

      在FriendGroup类中

       @ManyToOne
        @JoinColumn(name = "uid",nullable = false)
        private User user;

      其中呢,name ="uid" 代表,FriendGroup表中的uid字段对应的是User表中的主键(id)字段。也就是说,当我们查询一个User实体时,friendGroups字段会带上该用户下的好友分组。当然其中还涉及到了懒加载机制。(具体我也没研究,大体就是说,当调用 getFriendGroups()方法的时候才会根据关系从数据库查询相应的结果,这个后边会有体现)

      其次,LayIM加载中会把当前用户的群组信息直接加载出来。这里我们也可以直接用 @OneToMany实现,原理同上。(刚开始我做错了,我只是列出了该用户创建的群,没有考虑到用户加入的群)

      第三,@OneToOne 。这个注解呢在获取好友分组下的好友列表中用到了。比如,一个好友分组对应多个好友关系。那么每个好友关系从当前登录者的角度来说就是一对一的。这句话要怎么理解呢?就是说从程序上来讲,我们知道每个分组ID对应的多个UserId,因为表中存的是Id,但是我们取数据的时候需要把该用户的其他信息拿出来,那么使用@OneToOne注解,就会帮我们自动关联查询。代码如下:

        @OneToOne
        @JoinColumn(name="friend_uid")
        private User friend;

      代码中的friend_uid字段即用户id。(因为User类中的主键已经确定,所以不用再User类中增加 @OneToOne注解)

    代码实现

      (2017-11-7 日晚:此段代码已重构,为了写出我当时的思路,下文内容不会删掉,重构后代码讲解:http://www.cnblogs.com/panzi/p/7793854.html

      说了这么多,想必大家都有点晕了,下面就是代码实现了。在这里呢,我将拿好友分组举例,这个最简单。写一个单元测试,如下:

       public User getUser(Long userId){
            return userRepository.findOne(userId);
        }
       @Test
        public void testGetUserFriendGroups() {
            User user = userService.getUser(100000L);
            System.out.println("用户好友分组的个数为:"+user.getFriendGroups().size());
        }

      运行结果:

      

    小 tips 

      当你做单元测试的时候,会遇到懒加载没有Session的问题,如果遇到了,在字段上面添加 FetchType.EAGER即可

      @OneToMany(mappedBy = "user",fetch = FetchType.EAGER)

      但是程序运行可以不用加这个,可以在配置文件中增加open-in-view :true 解决

      

    JPA默认方法

      在讲解具体实现之前呢,还要介绍一下JpaRepository这个接口,这个玩意挺好玩的,除了默认的方法,还可以自定义比如 findBy,getBy,findBy..OrderBy..等等方法。这里只介绍当前用到的,至于没有用到的,有兴趣的同学可以自行研究。代码很简单,新建一个UserRepository继承自JpaRepository即可

    public interface UserRepository extends JpaRepository<User,Long> {
        
    }

      这样写了之后,UserRepository默认会有一些方法,增删改查或者分页。这里呢,我们没有用到分页,只是简单的查询。没错就是 findOne方法

      

    LayIM init接口实现

      好了,讲了这么多,可能大家都云里雾里的,没有关系,下面就进入LayIM init接口实现部分。当然作为初学者来说,我就使用了笨方法,为什么说笨方法呢,方法思路如下:

      

      方法笨就笨在里面的for循环。(应该是有优化的,后期在看)

      for(FriendGroup friendGroup : friendGroups) {
               List<RelationShip> relationShips = friendGroup.getRelationShips();
               List<UserViewModel> userViewModels = new ArrayList<UserViewModel>();
               //遍历 relationShips 获取userViewModels的集合
             for(RelationShip relationShip : relationShips){
                   UserViewModel userViewModel = LayimMapper.INSTANCE.mapUser(relationShip.getFriend());
                   userViewModels.add(userViewModel);
               }
               //获取主键
               Long friendGroupId = friendGroup.getId();
               for (FriendGroupViewModel viewModel : friends) {
                   if (viewModel.getId().equals(friendGroupId)) {
                       viewModel.setList(userViewModels);
                   }
               }
           }

      代码当中我使用了 LayimMapper,他依赖于一个实体转换工具:MapStruct。有兴趣大家自行去官网看看。不过我个人觉得有点类似代码生成器,因为在编译过后,target里面会多出一个类来。

      

      其实还是挺方便的,毕竟自己写也是这么写,这样还大大节约了时间。

      好了,本篇内容啰嗦了一大堆,我也不知道讲了些啥,总之就是我在学习之中的一些问题和开发思路。知识点很多,所以在我这里介绍的都是皮毛,要深追究下去,每个知识点都可以写上几篇。最后看一下运行效果。接口同上一篇的一样。

      

      对应数据库:

      

      

      

      后台执行的语句:

      

     总结

      本篇到此为止就结束啦,Jpa部分讲解的比较少,决定下一篇重点学习记录一下。这个基础接口实现了。LayIM还有一个getMembers接口,你是不是也会了呢?在我学习的过程中,包括关系注解,MapStruct应用和其他实践上自己给自己挖了很多坑,也出现过很多莫名奇妙的问题,有些东西自己动手了才知道。愉快的周末结束啦。我们下一篇见。

      下篇预告:从零一起学Spring Boot之LayIM项目长成记(四) Spring Boot  JPA 深入了解

  • 相关阅读:
    多线程
    事务的概念与四大特性(ACID)
    Error和Exception的区别
    运算符的使用与注意事项(二)
    运算符的使用与注意事项(一)
    GOJS使用--前端拓扑图
    python算法实现分治法demo
    MySQL主从复制配置
    MySQL锁
    show profile查看SQL执行生命周期
  • 原文地址:https://www.cnblogs.com/panzi/p/7788446.html
Copyright © 2011-2022 走看看