2

I hope this question is not opinion based, but here we go. I am developing a small API with spring boot, spring data jpa, and JWT Token as the authentication system. The API is pretty straightforward, is an exam generator, where you can register professor, course, question, choice etc.

For example: [13/11/2017 - edited to include 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;
}

Because the authentication system is JWT token for all queries I have to include the professor's information.

For example

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);
}

The problem is that for every crud query I will have to write my own custom query, because of professor's information and also the enabled field.

I know there is a way to extends the PagingAndSortingRepository and write your own custom query, like this:

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
}

This CustomPagingAndSortRepository would work perfectly for all classes if I only had enabled to worry about. This class will work perfectly for Course because e.professor = ?#{principal.professor} will find the Professor there. But it will not work for Question, for example, because I need a join with the course class to get the professor, like e.course.professor = ?#{principal.professor}

The point is f I put an association with Professor in all classes I can make that generic code work, save tons of code but sacrificing database design's normalization. The question is: Is there another way? or maybe is it valid to do it?

PS: The Question, Course etc will never change owner, it means that only the Professor who created will be able to use it.

  • is `principal.professor` a logged in user? – Ayo K Nov 10 '17 at 17:01
  • Do you have id columns for all objects setup with a foreignkey relation in the Question and Course? AAre you relying on hibernate to create the tables and columns, or are you using something flyway, or are you relying on manual SQL scripts? – Pytry Nov 10 '17 at 18:15
  • @AyoK Yes, it is. – William Suane Nov 12 '17 at 01:20
  • @Pytry, I using Spring Data JPA for everything, including the table generation, so yes, I have, didn't write here to make it shorter. But all entity classes extends from an Abstract Entity with `id` and `enabled` fields. – William Suane Nov 12 '17 at 01:23

2 Answers2

2

Why dont you create a class annotated @Service that Autowires your CourseRepository then pass id, principal.professor, enabled into it, then your repository will have the method Course findByIdAndProfessorAndEnabled(Long id, String professor, Boolean enabled). Code Snippet:

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);
    }
}

EDIT: For Hibernate Models

If you always want enabled set to true in your queries you can use the @Where annotation on your model

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

I don't know if this approach would also work for your principal but you can try it

Ayo K
  • 1,719
  • 2
  • 22
  • 34
  • The point is, I don't want to write all queries again, for example, if you use `findAll` from CrudRepository, I would have to create my own query to `findAll` for each class, because I need `professor` and `enabled`. I was thinking about something like this answer: [https://stackoverflow.com/a/44348848/5447324] – William Suane Nov 13 '17 at 16:35
  • My answer does not require you writing any queries – Ayo K Nov 13 '17 at 16:37
  • Sorry, I should have included writing queries and/or overriding methods. Your answer means that I would have to override all basic methods. – William Suane Nov 13 '17 at 16:42
  • 1
    That is not overriding a method. By following the naming convention @Ayok used, you are creating your own new method. Spring Boot takes the method names syntax and automatically converts it into a query. Other basic methods, such as "findAll", are also automatically created for you. Ayo injected the repository into a service, but you could inject it anywhere you want (Controller for example) and add your business logic around the call to the repository auto-generated methods. – Pytry Nov 13 '17 at 19:13
  • @Pytry Look my answer below. – William Suane Nov 16 '17 at 23:20
0

Ok, I will answer my own question. So, the question was:

The point is if I put an association with Professor in all classes I can make that generic code work, save tons of code but sacrificing database design's normalization. The question is: Is there another way? or maybe is it valid to do it?

And I actually did, based on this answer and it reduced a lot of code.

Now I have my model classes like this (Included Professor in all other classes too):

@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;
    // ...
}

And a CustomPagingAndSortRepository like this

@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();
}

Doing this, all my basic queries will include automatically the name of the entity, the professor and if it is enabled. For example, my QuestionRepository is like this:

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);
}

Now I don't have to worry about creating a custom delete, or custom findALL or deleteAll etc. to include Professor and enabled for every single class that I create.

So, the answer to my own question: in my opinion, it is valid to add an association with a professor in all classes (in cases like mine, using JWT Authentication). I was able to reduce a lot of code and it is a 100% Spring solution.

PS: Didn't test findAll with Sort and Pageable yet.