【问题标题】:Change DB design to improve Spring Data JPA soft delete and authentication on queries更改数据库设计以改进 Spring Data JPA 软删除和查询身份验证
【发布时间】:2018-04-23 20:11:50
【问题描述】:

我希望这个问题不是基于意见的,但我们开始吧。 我正在开发一个使用 spring boot、spring data jpa 和 JWT Token 作为身份验证系统的小型 API。 API 非常简单,是一个考试生成器,您可以在其中注册教授、课程、问题、选择等。

例如: [13/11/2017 - 编辑以包含 AbstractEntity]

@MappedSuperclass
public class AbstractEntity implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    protected Long id;
    protected boolean enabled = true;
    //equals, hashcode...
}

public class Professor extends AbstractEntity{ 
    private String email;
}

public class Course extends AbstractEntity {
    @ManyToOne(optional = false)
    private Professor professor;  
}

public class Question extends AbstractEntity {
    @ManyToOne(optional = false)
    private Course course;
}

因为身份验证系统是所有查询的 JWT 令牌,所以我必须包含教授的信息。

例如

public interface CourseRepository extends PagingAndSortingRepository<Course, Long> {
    @Query("select c from Course c where c.id = ?1 and c.professor = ?#{principal.professor} and c.enabled = true")
    Course findOne(Long id);
}

问题在于,由于教授的信息以及启用的字段,对于每个 crud 查询,我都必须编写自己的自定义查询。

我知道有一种方法可以扩展 PagingAndSortingRepository 并编写您自己的自定义查询,如下所示:

public interface CustomPagingAndSortRepository<T, ID extends Serializable> extends PagingAndSortingRepository<T, ID> {
    @Override
    @Query("select e from #{#entityName} e where e.id = ?1 and e.professor = ?#{principal.professor} and e.enabled = true")
    T findOne(ID id);
    //More fields overrided here
}

如果我只启用担心,这个 CustomPagingAndSortRepository 将完美地适用于所有类。这门课非常适合 Course,因为 e.professor = ?#{principal.professor} 会在那里找到教授。但它不适用于问题,例如,因为我需要加入课程班级才能获得教授,例如e.course.professor = ?#{principal.professor}

关键是如果我在所有课程中都与教授建立关联,我可以使通用代码工作,节省大量代码但牺牲数据库设计的规范化。 问题是:还有其他方法吗?或者这样做是否有效?

PS:问题,课程等永远不会改变所有者,这意味着只有创建的教授才能使用它。

【问题讨论】:

  • principal.professor 是登录用户吗?
  • 您是否在问题和课程中为所有具有外键关系的对象设置了 id 列? AA你是依靠休眠来创建表和列,还是你在使用flyway,或者你是依靠手动SQL脚本?
  • @AyoK 是的。
  • @Pytry,我将 Spring Data JPA 用于所有内容,包括表生成,所以是的,我有,没有写在这里以使其更短。但是所有实体类都从具有idenabled 字段的抽象实体扩展而来。

标签: java spring spring-boot spring-data-jpa


【解决方案1】:

你为什么不创建一个注解@Service 的类,它会自动连接你的CourseRepository,然后将id, principal.professor, enabled 传递给它,那么你的存储库将拥有Course findByIdAndProfessorAndEnabled(Long id, String professor, Boolean enabled) 方法。 代码片段:

public interface CourseRepository extends PagingAndSortingRepository<Course, Long> {
    Course findByIdAndProfessorAndEnabled(Long id, Professor professor, Boolean enabled);
}

@Service
public class CourseService{
    @Autowired
    CourseRepository courseRepository;

    //pass in principal.professor from your controller or from wherever this is called
    public Course findByIdAndProfessorAndEnabled(Long id, Professor  professor, Boolean enabled){
        return courseRepository.findByIdAndProfessor(id, professor, enabled);
    }
}

编辑:对于 Hibernate 模型

如果您总是希望在查询中将 enabled 设置为 true,您可以在模型上使用 @Where 注释

@MappedSuperclass
@Where(clause="is_enabled=true")
public class AbstractEntity implements Serializable {
.
.
.
@Column(name="is_enabled")
protected boolean enabled = true;
...

我不知道这种方法是否也适用于您的委托人,但您可以尝试一下

【讨论】:

  • 重点是,我不想再次编写所有查询,例如,如果您使用来自 CrudRepository 的findAll,我将不得不为每个类创建自己的查询到findAll ,因为我需要professorenabled。我在想这样的答案:[stackoverflow.com/a/44348848/5447324]
  • 我的回答不需要你写任何查询
  • 对不起,我应该包括编写查询和/或覆盖方法。您的回答意味着我将不得不覆盖所有基本方法。
  • 这不是覆盖方法。通过遵循 @Ayok 使用的命名约定,您正在创建自己的新方法。 Spring Boot 采用方法名称语法并自动将其转换为查询。其他基本方法,例如“findAll”,也会自动为您创建。 Ayo 将存储库注入到服务中,但您可以将其注入到任何您想要的位置(例如控制器),并在调用存储库自动生成的方法周围添加您的业务逻辑。
  • @Pytry 在下面看我的答案。
【解决方案2】:

好的,我会回答我自己的问题。所以,问题是:

关键是如果我在所有课程中都与教授建立关联,我可以使通用代码工作,节省大量代码但牺牲数据库设计的规范化。问题是:还有其他方法吗?或者这样做是否有效?

我确实做到了,基于this answer,它减少了很多代码。

现在我有这样的模型类(在所有其他类中也包括Professor):

@MappedSuperclass
public class AbstractEntity implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    protected Long id;
    @Column(columnDefinition = "boolean default true", nullable = false)
    private boolean enabled = true;
// ...
}

@Entity
public class Course extends AbstractEntity {
    private String name;
    @ManyToOne(optional = false)
    private Professor professor;
    //...
}

@Entity
public class Question extends AbstractEntity {
    private String title;
    @ManyToOne(optional = false)
    private Course course;
    @ManyToOne(optional = false)
    private Professor professor;
    // ...
}

还有这样的 CustomPagingAndSortRepository

@NoRepositoryBean
public interface CustomPagingAndSortRepository<T extends AbstractEntity, ID extends Long>
        extends PagingAndSortingRepository<T,ID> {
    @Override
    @Query("select e from #{#entityName} e where e.professor = ?#{principal.professor} and e.enabled = true")
    Iterable<T> findAll(Sort sort);

    @Override
    @Query("select e from #{#entityName} e where e.professor = ?#{principal.professor} and e.enabled = true")
    Page<T> findAll(Pageable pageable);

    @Override
    @Query("select e from #{#entityName} e where e.id =?1 and e.professor = ?#{principal.professor} and e.enabled = true")
    T findOne(Long id);

    @Override
    default boolean exists(Long id){
        return findOne(id) != null;
    }

    @Override
    @Query("select e from #{#entityName} e where e.professor = ?#{principal.professor} and e.enabled = true")
    Iterable<T> findAll();

    @Override
    @Query("select e from #{#entityName} e where e.professor = ?#{principal.professor} and e.enabled = true")
    Iterable<T> findAll(Iterable<ID> iterable);

    @Override
    @Query("select count(e) from #{#entityName} e where e.professor = ?#{principal.professor} and e.enabled = true")
    long count();

    @Override
    @Transactional
    @Modifying
    @Query("update #{#entityName} e set e.enabled=false where e.id=?1 and e.professor = ?#{principal.professor}")
    void delete(Long id);

    @Override
    @Transactional
    @Modifying
    default void delete(T t){
        delete(t.getId());
    }

    @Override
    @Transactional
    @Modifying
    default void delete(Iterable<? extends T> iterable){
        iterable.forEach(entity -> delete(entity.getId()));
    }

    @Override
    @Transactional
    @Modifying
    @Query("update #{#entityName} e set e.enabled=false where e.professor = ?#{principal.professor}")
    void deleteAll();
}

这样做,我的所有基本查询将自动包括实体名称、教授以及是否启用。比如我的 QuestionRepository 是这样的:

public interface QuestionRepository extends CustomPagingAndSortRepository<Question, Long> {
    @Query("select q from Question q where q.course.id = ?1 and q.title like %?2% and q.professor = ?#{principal.professor} and q.enabled = true")
    List<Question> listQuestionsByCourseAndTitle(long courseId, String title);
}

现在我不必担心创建自定义删除、自定义 findALL 或 deleteAll 等,以便为我创建的每个类包含 Professorenabled

所以,我自己的问题的答案:在我看来,在所有课程中添加与教授的关联是有效的(在像我这样的情况下,使用 JWT 身份验证)。我能够减少很多代码,这是一个 100% 的 Spring 解决方案。

PS:还没有用SortPageable 测试findAll

【讨论】:

    猜你喜欢
    • 2018-07-13
    • 1970-01-01
    • 1970-01-01
    • 2019-07-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-02
    相关资源
    最近更新 更多