Дизайн БД без прохождения вокруг jdbc

У меня проблема с дизайном db, с которой я столкнулся с одним из моих проектов. Я пытаюсь реализовать службу, и часть этой службы является слоем db. Это настройка, так что у меня есть вспомогательные классы, которые выполняют методы get/update в базе данных и слой поверх них, который является janitor. Для примера:

public class GetStudentDBHelper {
   public List<Student> get(List<Integer> ids) {
      Conn getConnection...
      // run sql query and construct returning Student objects
   }
   public List<Student> get(List<Classroom> byClassroom) {
      // get all students in passed in classrooms
      // run sql query and construct returning Student objects
   }
}

public class StudentJanitor {
   public GetStudentDBHelper getStudentDBHelper;
   public UpdateStudentDBHelper updateStudentDBHelper;
   public UpdateClassroomDBHelper updateClassroomDBHelper;

   public List<Student> getStudents(List<Integer> ids) {
       return getStudentDBHelper.get(ids);
   }

   public void saveStudents(List<Students> students, int classRoomid) {
       Connection conn = Pool.getConnection(); // assume this gives a jdbc
       conn.autocommit(false);

       try {
           try 
           {
              updateStudentDBHelper.saveForClassroom(students, classRoomid, conn);
              updateClassroomDBHelper.markUpdated(classRoomid, conn);
              conn.commit();
           }
           catch
           {
              throw new MyCustomException(ErrorCode.Student);
           }
       }
       catch (SQLException c)
       {
           conn.rollback();
       }
       finally {
           conn.close();
       }
}

public class ClassroomJanitor{
   public void saveClassRoon(List<Classrooms> classrooms) {
       Connection conn = Pool.getConnection()// assume this gives a jdbc
       conn.autocommit(false);

       try {

           try {
              updateClassroomDBHelper.save(classrooms, conn);
              updateStudentDBHelper.save(classrooms.stream().map(Classroom::getStudents).collect(Collections.toList()), conn);
              conn.commit();
           }
           catch {
              throw new MyCustomException(ErrorCode.ClassRoom);
           }
       }
       catch (SQLException c)
       {
           conn.rollback();
       }
       finally {
           conn.close();
       }
}...

public class GetClassroomDBHelper{}...
public class UpdateClassroomDBHelper{}...

В классах обновления db все составляются несколько других обновителей, если им нужно обновлять значения в других таблицах (т.е. сохранение студента означает, что я должен коснуться таблицы классов, в которой ученик должен обновить свое последнее обновленное время, например).

Проблема, с которой я столкнулась, связана с классами обновления db, я должен передать соединение из моего класса Janitor, если я касаюсь нескольких таблиц, чтобы иметь транзакции и их возможности отката. См. Выше, что я имею в виду. Есть лучший способ сделать это? Этот тип try, catch, pass to connect to db helpers должен быть выполнен для любой операции с несколькими транзакциями в моих janitors.

Короче говоря, вы можете видеть, что код, как правило, подобен этому дублируемому по нескольким методам:

       Connection conn = Pool.getConnection()// assume this gives a jdbc
       conn.autocommit(false);

       try {

           try {
              //do some business logic requiring Connection conn
           }
           catch {
              throw new MyCustomException(ErrorCode);
           }
       }
       catch (SQLException c)
       {
           conn.rollback();
       }
       finally {
           conn.close();
       }

Ответ 1

Всякий раз, когда у вас есть дублируемая последовательность кода, но она отличается только некоторыми частями, вы можете использовать метод шаблона .

В вашем случае я бы представил класс TransactionTemplate и использовал интерфейс обратного вызова для частей, которые отличаются. Например.

public class TransactionTemplate {

    private DataSource dataSource;

    public TransactionTemplate(DataSource dataSource) {
        this.dataSource = Objects.requireNonNull(dataSource);

    }

    public <T> T execute(TransactionCallback<T> transactionCallback) throws Exception {
        Connection conn = dataSource.getConnection();// assume this gives a jdbc
        try {
            conn.setAutoCommit(false);
            T result = transactionCallback.doInTransaction(conn);
            conn.commit();
            return result;
        } catch (Exception e) {
            conn.rollback();
            throw e;
        } finally {
            conn.close();
        }
    }
}

Интерфейс обратного вызова будет выглядеть следующим образом:

public interface TransactionCallback<T> {
    public T doInTransaction(Connection conn) throws Exception;
}

Как вы можете видеть, TransactionTemplate управляет транзакцией, а TransactionCallback реализует логику, которая должна выполняться в одной транзакции.

После этого ваш клиентский код будет выглядеть следующим образом:

public class StudentJanitor {

    private TransactionTemplate transactionTemplate;

    StudentJanitor(DataSource dataSource) {
        transactionTemplate = new TransactionTemplate(dataSource);
    }

    public void saveStudents(List<Students> students, int classRoomid) {
        SaveStudentsTransaction saveStudentsTransaction = new SaveStudentsTransaction(students, classRoomid);
        transactionTemplate.execute(saveStudentsTransaction);
    }

}

и логика помещается в TransactionCallback

public class SaveStudentsTransaction implements TransactionCallback<Void> {

    public GetStudentDBHelper getStudentDBHelper;
    public UpdateStudentDBHelper updateStudentDBHelper;
    public UpdateClassroomDBHelper updateClassroomDBHelper;

    private List<Students> students;
    private int classRoomid;

    public SaveStudentsTransaction(List<Students> students, int classRoomid) {
        this.students = students;
        this.classRoomid = classRoomid;
    }

    @Override
        public Void doInTransaction(Connection conn) throws Exception {
            try 
            {
               updateStudentDBHelper.saveForClassroom(students, classRoomid, conn);
               updateClassroomDBHelper.markUpdated(classRoomid, conn);
               conn.commit();
            }
            catch
            {
               throw new MyCustomException(ErrorCode.Student);
            }
            return null;
        }

}

Ответ 2

Две основные проблемы, с которыми вы сейчас сталкиваетесь, - это код плиты котла для повторяющихся задач, связанных с соединением (get/execute/close и т.д.), и инфраструктуры для получения того же соединения через границы методов. Первый обычно решается с помощью Шаблон шаблона, а последний используя переменные Threadlocal, чтобы передать соответствующее соединение между методами. Эти проблемы были решены в Java-мире давно, но вам придется полагаться на фреймворк, например Spring (JDBC-шаблон) и т.д., которые имеют эту функцию за последнее десятилетие или около того, или вам нужно будет перевернуть вырезать вниз по этой инфраструктуре. Если вы заинтересованы в последнем, вы можете сделать намек на подобные аттестаты, общие для Github, например this.