记录一下即将重构的项目 spring boot + restful

本贴最后更新于 2100 天前,其中的信息可能已经沧海桑田

真的很心累,说实话自己真的不想重构,因为自己真的很喜欢 spring data jpa,他的简洁方便再加上 jdk 1.8 的特性,真的不忍心将他从我项目中剥离,但是他的多对多问题真的给我带来了太多的烦恼,自己能力不足以解决这些问题,一路下来,磕磕碰碰,最终却还是不得不放弃它,又爱又恨。这篇博客,记录一下到目前为止自己不太满意的的一个项目吧,他在刚才已经被 mybatis 完全替换,为他保留一个分支。

项目地址:XIAOMING

分支地址:XIAOMING-JPA

能带给你什么

  1. spring data jpa 无限查询的一些解决办法
  2. spring data redis 新的配置方式
  3. spring boot restful 公共部分抽取

技术选型

  • 核心框架:spring boot
  • 持久层:spring data jpa
  • 数据库:mysql
  • 安全:spring security oauth2
  • 加密:jwt
  • 缓存:redis

带来的烦恼

  1. mysql 多对多的无限查询问题
  2. redis 序列化问题

mysql 多对多的无限查询问题

就像所描述那样,sys_user 表里面有 roles 字段存放所有权限, sys_role 里面有 users 字段。如下

public class SysUser extends BaseEntity implements UserDetails {

    // ...

    /**
     * 当前用户的权限
     */
    @ManyToMany(fetch = FetchType.EAGER)
    @JsonIgnoreProperties(value = "users")
    @JoinTable(name = "sys_user_role",
            joinColumns = {@JoinColumn(name = "user_id", nullable = false)},
            inverseJoinColumns = {@JoinColumn(name = "role_id", nullable = false)})
    private List<SysRole> roles;

    // ...
}
public class SysRole extends BaseEntity {

    // ...

    /**
     * 当前角色的菜单
     */
    @JsonIgnoreProperties(value = "roles")
    @ManyToMany(cascade = CascadeType.MERGE, fetch = FetchType.EAGER)
    @JoinTable(name = "sys_permission_role", joinColumns = @JoinColumn(name = "role_id"),
            inverseJoinColumns = @JoinColumn(name = "permission_id"))
    private List<SysPermission> permissions = new ArrayList<>();

    /**
     * 当前角色对应的用户
     * 双向映射造成数据重复查询死循环问题
     */
    @ManyToMany(mappedBy = "roles")
    private List<SysUser> users = new ArrayList<>();

}
public class SysPermission extends BaseEntity {
    // ...

    /**
     * 菜单角色
     * 双向映射造成数据重复查询死循环问题
     */
    @ManyToMany(mappedBy = "permissions")
    private List<SysRole> roles = new ArrayList<>();
}

存放拥有当前角色的所有用户,然后带来的结果是,他们两一直互相无限查询,打印无数 sql 语句最后堆栈溢出。尝试过很多解决办法,大概有如下几种:

  • @JsonIgnore 注解,但是在数据库查询出来的时候会忽略掉此字段,所以不可行。
  • @JsonIgnoreProperties 注解,奇怪的是时而有效时而无效。
  • @Proxy(lazy = false) 注解,无效
  • fetch = FetchType.EAGER 属性,需要在配置文件中添加如下配置才有小,不然要产生一个 什么 bag 异常
jpa:
  properties:
    hibernate:
      enable_lazy_load_no_trans: true

但是会带来 N+1 问题,查询效率有所降低,不过小项目无所谓=0=

  • @ToString(exclude = {"users", "permissions"}) 同时需要生成的 tostring 方法忽略掉这些字段,不然在使用时会报 LazyInitializationException ... no session 错误。

redis 序列化问题

我缓存选择的是 redis 缓存,而在将他存入的时候遇到了一个 spring data jpa 分页查询无法序列化的问题,因为他没有默认的无参构造,因而我的分页查询无法使用 redis 缓存。为啥不自己写一个?懒=-=

redis 的 CacheManager 网上搜到的方式大多不管用,我的方式如下:

    @Bean
    @Override
    public CacheManager cacheManager() {
        // 配置在这里配置
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofHours(12)) // 过期时间
                .prefixKeysWith(applicationProperties.getName()) // 缓存前缀
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(keySerializer())) // 序列化键
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(valueSerializer())) // 序列化值
                .disableCachingNullValues();
        // 创建缓存管理器
        return RedisCacheManager
                .builder(RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory))
                .cacheDefaults(redisCacheConfiguration)
                .transactionAware()
                .build();
    }

自己也写了 gsonFastJson 的序列化,有兴趣的可以看看 github 项目的 RedisConfig

然而真正让我放弃 spring data jpa 的原因,其实是因为在我前几天修改后,尝试查询,第一次查询成功并存入 redis,然后再次查询他就报序列化错误,我尝试解决了三天,实在找不到解决的办法了,也在 stackoverflow 发起提问但是依旧没有办法解决,所以只有完全放弃 spring data jpa 换成 mybatis 试试了。

公共部分抽取

对于一个 restful 风格的项目,他的 controller、service、repository 层都是有公共的部分的,如果不抽取,需要写很多重复的代码,作为一个合(zhuang)格(bi)的 JAVA 程序员,肯定是不容许他的存在,更何况还会带来一处修改处处修改的尴尬,所以对他进行了公共部分抽取。

BaseEntity

提取实体类的公共字段

BaseRepository

公共的仓库基类,一般适用于对公共字段的条件查询等。

BaseService

service 公共接口

BaseServiceImpl
BaseController

最重要的,restful 风格基类 controller

RestResource 是对单个资源的封装,使用 spring boot hateoas 生成对应的 hateoas

RestResources 是对多个资源集合的封装,使用 spring boot hateoas 生成对应的 hateoas

为什么不用 spring data rest

他不能用缓存!!!他不能用缓存!!!他不能用缓存!!!我找了一段时间的资料,都没找到,难受。

为了加 hateoas 真的累死我了,到后面还不满意,分页的 hateoas 我用 aop 进行的单独封装,通过添加注解进行拦截再次封装,不过尝试了很多很多办法,最后只能使用字符串拼接。

感受

因为前面说的一个 redis 的问题不得不放弃,自己还是太菜了。这是最后一个项目,完成了他,自己就要开始考研道路,估计基本不会再去写项目了。不想留下遗憾,他的结构也是我比较满意的,不过是实话,不太喜欢分层架构,跟喜欢一捅到底的架构,不过也希望这个项目不会让自己失望,加油!

  • Spring

    Spring 是一个开源框架,是于 2003 年兴起的一个轻量级的 Java 开发框架,由 Rod Johnson 在其著作《Expert One-On-One J2EE Development and Design》中阐述的部分理念和原型衍生而来。它是为了解决企业应用开发的复杂性而创建的。框架的主要优势之一就是其分层架构,分层架构允许使用者选择使用哪一个组件,同时为 JavaEE 应用程序开发提供集成的框架。

    944 引用 • 1459 回帖 • 17 关注
  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3187 引用 • 8213 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...