Пример реализации слоя приложения persistence layer без использования ORM фреймворка

от автора

Слой приложения persistence layer является в определённом смысле уникальным в смысле узкой направленности его функционала по сравнению с другими слоями приложения. Если рассматривать его только для работы с реляционными базами данных, то реализацию функционала слоя можно разбить на два основных варианта — с использованием ORM фреймворка и без использования ORM фреймворка. Каждый из этих вариантов можно реализовать достаточно универсальным образом.

Реализация с использованием ORM фреймворка прекрасно описана в разделах 18.1 и 18.2 в книге Бауэр К., Кинг Г., Грегори Г. Java Persistence API и Hibernate. ДМК Пресс, 2017.

В этой статье рассмотрен пример реализации слоя persistence layer без использования ORM фреймворка. Предлагаемое решение является простым и в тоже время достаточно универсальным для использования в языках программирования, поддерживающих объектную модель.

Структуру слоя persistence layer рассмотрим в виде трёхуровневой иерархии функционала.
Эти уровни иерархии можно рассматривать как подслои persistence layer.

  1. Фасад слоя — набор объектов доступа к внешним персистентным данным (DAO объектов). Через фасад происходит доступ к функционалу слоя из вышележащих слоёв приложения. Фасад скрывает детали реализации работы с базой данных от вышележащих слоёв приложения.

  2. Механизмы обработки персистентных данных.

  3. Механизмы доступа к реляционным базам данных.

Модель данных слоя persistence layer в данном примере представлена классом Factor. Его структура данных соответствует структуре данных в строке таблицы tblFactors в базе данных.

    public class Factor     {         public int Id;         public string Name;         public decimal Value;     }

Рассмотрим примеры кода на C#, который реализует функционал слоя.

  1. Объекты доступа к внешним персистентным данным (DAO объекты) являются наследниками базового класса ABaseDAO.

    /// <summary>базовый класс DAO объектов</summary>     public abstract class ABaseDAO     {         protected IPersistenceManager persistenceManager;     }      /// <summary>имплиментация DAO объекта для работы с сущностью Factor</summary>     public class FactorDAO : ABaseDAO     {         /// <summary>         /// в конструктор через параметр инжектируется объект типа SqlPersistenceManager         /// </summary>         public FactorDAO(IPersistenceManager persistenceManager)         {             this.persistenceManager = persistenceManager;         }          /// <summary>         /// вставка новой строки в таблицу tblFactors         /// </summary>         public void Insert(Factor entity)          {  var sqlQuery = "INSERT INTO tblFactors(Id,Name,Value) VALUES(@Id,@Name,@Value) "; var listDbParameters = new List<DbParameter> { persistenceManager.CreateQueryParameter("@Id", entity.Id) };  listDbParameters.Add(persistenceManager.CreateQueryParameter("@Name", entity.Name)); listDbParameters.Add(persistenceManager.CreateQueryParameter("@Value", entity.Value));  persistenceManager.PersistData(sqlQuery, listDbParameters.ToArray()); }          /// <summary>         /// обновление данных строки в таблице tblFactors         /// </summary>         public void Update(Factor entity)          {  var sqlQuery = "UPDATE tblFactors SET Name=@Name, Value=@Value WHERE Id=@Id "; var listDbParameters = new List<DbParameter> { persistenceManager.CreateQueryParameter("@Id", entity.Id) };  listDbParameters.Add(persistenceManager.CreateQueryParameter("@Name", entity.Name)); listDbParameters.Add(persistenceManager.CreateQueryParameter("@Value", entity.Value));  persistenceManager.PersistData(sqlQuery, listDbParameters.ToArray()); }                  /// <summary>         /// удаление строки из таблицы tblFactors         /// </summary> public void Delete(Factor entity)          {  var sqlQuery = "DELETE FROM tblFactors WHERE Id=@Id "; var listDbParameters = new List<DbParameter> { persistenceManager.CreateQueryParameter("@Id", entity.Id) }; persistenceManager.PersistData(sqlQuery, listDbParameters.ToArray()); }          /// <summary>         /// извление строки данных из таблицы tblFactors по первичному ключу Id таблицы         /// </summary>         public List<Factor> SelectById(int id)          {              var sqlQuery = "SELECT Id, Name, Value FROM tblFactors WHERE Id=@Id ";              var listDbParameters = new List<DbParameter> { persistenceManager.CreateQueryParameter("@Id", id) };             DbParameter[] dbParameters = listDbParameters.ToArray();             DataTable dataTable = persistenceManager.RetrieveData(sqlQuery, dbParameters);             List<Factor> list = convertDataTableToEntityList(dataTable);              return list;         }         /// <summary>         /// В этом методе данные из объекта типа DataTable конвертируются в коллекцию объектов типа Factor         /// </summary>         protected List<Factor> convertDataTableToEntityList(DataTable dataTable)          {  // ........................ }     } 

2. Объекты, используемые в механизме обработки персистентных данных, реализуют интерфейс IPersistenceManager.

/// <summary>базовый интерфейс механизма обработки персистентных данных</summary>     public interface IPersistenceManager     {         DataTable RetrieveData(string strQuery, DbParameter[] sqlQueryParams);         DataTable RetrieveData(string strQuery);          void PersistData(string strQuery, DbParameter[] sqlQueryParams);         void PersistData(string strQuery);          DbParameter CreateQueryParameter(string parameterName, object value);     }  /// <summary>абстрактный базовый класс механизма обработки персистентных данных</summary>     public abstract class APersistenceManager : IPersistenceManager     {         /// <summary>Объект для получения соединения с бд</summary>         protected IDbConnectionManager connManager;          #region запросы на извлечение данных из БД         public DataTable RetrieveData(string strQuery, DbParameter[] sqlQueryParams)         {             DataTable dataTable = new DataTable();             DbCommand command = CreateCommand(strQuery, connManager.GetConnection());             AddQueryParameters(command, sqlQueryParams);             DbDataAdapter adapter = CreateDataAdapter(command);             adapter.Fill(dataTable);             return dataTable;         }          public DataTable RetrieveData(string strQuery)         {             return RetrieveData(strQuery, null);         }         #endregion          #region запросы на изменение данных в БД         public void PersistData(string strQuery, DbParameter[] sqlQueryParams)         {             DbCommand command = CreateCommand(strQuery, connManager.GetConnection());             AddQueryParameters(command, sqlQueryParams);             command.ExecuteNonQuery();         }          public void PersistData(string strQuery)         {             PersistData(strQuery, null);         }         #endregion          #region методы, функционал которых необходимо переопределить в зависимости от используемого типа базы данных         protected abstract DbCommand CreateCommand(string strQuery, DbConnection conn);          protected abstract DbDataAdapter CreateDataAdapter(DbCommand command);          public abstract DbParameter CreateQueryParameter(string parameterName, object value);         #endregion           /// <summary>         /// присоединяет коллекцию параметров, используемых в запросе к базе данных, к объекту DbCommand         /// </summary>         protected void AddQueryParameters(DbCommand command, DbParameter[] queryParams)         {             if (queryParams != null)             {                 foreach (DbParameter param in queryParams)                 {                     command.Parameters.Add(param);                 }             }         }     } 

Если в приложении используется несколько типов баз данных, то для каждого типа должен быть реализована пара объектов — PersistenceManager + ConnectionManager.
Для работы с базами данных Microsoft Sql server — это объекты типа SqlPersistenceManager и SqlConnectionManager.
Для работы с базами данных Oracle — это объекты типа OraclePersistenceManager и OracleConnectionManager.

    /// <summary>     /// имплиментация функционала механизма обработки персистентных данных для бд ms sql server     /// </summary>     public class SqlPersistenceManager : APersistenceManager     {         /// <summary>         /// в конструктор через параметр инжектируется объект типа SqlConnectionManager         /// </summary>         public SqlPersistenceManager(ISqlConnectionManager connMgr)         {             this.connManager = connMgr;         }          #region override members         protected override DbCommand CreateCommand(string strQuery, DbConnection conn)         {             DbCommand cmd = new SqlCommand(strQuery, (SqlConnection)conn);              return cmd;         }          protected override DbDataAdapter CreateDataAdapter(DbCommand command)         {             return new SqlDataAdapter((SqlCommand)command);         }          /// <summary>Метод для создания параметра запроса</summary>         public override DbParameter CreateQueryParameter(string parameterName, object value)         {             return new SqlParameter(parameterName, value);         }          #endregion     }      public class OraclePersistenceManager : APersistenceManager     { /// <summary> /// в конструктор через параметр инжектируется объект типа OracleConnectionManager /// </summary>         public OraclePersistenceManager(IOracleConnectionManager connMgr)         {             this.connManager = connMgr;         }          #region override members         protected override DbCommand CreateCommand(string strQuery, DbConnection conn)         {             DbCommand cmd = new OracleCommand(strQuery, (OracleConnection)conn);             return cmd;         }          protected override DbDataAdapter CreateDataAdapter(DbCommand command)         {             return new OracleDataAdapter((OracleCommand)command);         }          /// <summary>Метод для создания параметра запроса</summary>         public override DbParameter CreateQueryParameter(string parameterName, object value)         {             return new OracleParameter(parameterName, value);         }         #endregion     } 

3. Объекты, используемые в механизме доступа к реляционным базам данных, реализуют интерфейс IDbConnectionManager.

    /// <summary>     /// базовый интерфейс механизма доступа к реляционным базам данных     /// </summary>     public interface IDbConnectionManager     {         DbConnection GetConnection();     }      /// <summary>     /// базовый класс, реализующий функционал механизма доступа к реляционным базам данных     /// </summary>     public abstract class ADbConnectionManager : IDbConnectionManager     {         #region поля и свойства класса         /// <summary>Объект соединения с базой данных</summary>         protected DbConnection dbConnection = null;          /// <summary>Строка соединения с базой данных</summary>         protected abstract string connectionString { get; }          #endregion          /// <summary>         /// Возвращает объект соединения с базой данных.          /// </summary>         public DbConnection GetConnection()         {             if (dbConnection == null || dbConnection.State != ConnectionState.Open)             {                 createConnection();             }             return dbConnection;         }          /// <summary>         /// Создаёт объект соединения с базой данных         /// </summary>         protected abstract void createConnection();     }      public interface ISqlConnectionManager : IDbConnectionManager     {     }      /// <summary>     /// класс, реализующий функционал механизма доступа к базе данных ms sql server     /// </summary>     public class SqlConnectionManager : ADbConnectionManager, ISqlConnectionManager     {         protected override string connectionString          {              get              {                  return AppConfigSettings.SqlConnectionString;              }          }          /// <summary>         /// Создаёт объект соединения с базой данных         /// </summary>         protected override void createConnection()         {             dbConnection = new SqlConnection(connectionString);             dbConnection.Open();         }     }      public interface IOracleConnectionManager : IDbConnectionManager     {     }      public class OracleConnectionManager : ADbConnectionManager, IOracleConnectionManager     {         protected override string connectionString         {             get             {                 return AppConfigSettings.ConnectionString;             }         }          /// <summary>         /// Создаёт объект соединения с базой данных         /// </summary>         protected override void createConnection()         {             dbConnection = new OracleConnection(connectionString);             dbConnection.Open();         }     } 

Рассмотрим случай, когда приложение работает с несколькими базами данных ms sql server:

  1. history — база данных телеметрии;

  2. ius — база нормативно-справочных данных.

Для соединения с каждой из этих баз данных необходимо добавить в приложение класс, который создаёт объект соединения с ней. Этот класс инжектируется в конструктор класса SqlPersistenceManager при помощи Inversion of control фреймворка.

    /// <summary>     /// перегруженный класс, реализующий функционал для соединения с базой данных history     /// </summary>     public class HistorySqlConnectionManager : SqlConnectionManager     {         protected override string connectionString         {             get             {                 return AppConfigSettings.HistoryConnectionString;              }         }     }      /// <summary>     /// перегруженный класс, реализующий функционал для соединения с базой данных ius     /// </summary>     public class IusSqlConnectionManager : SqlConnectionManager     {         protected override string connectionString         {             get             {                 return AppConfigSettings.IusConnectionString;              }         }     } 

При работе с объектами ConnectionManager может возникнуть следующая проблема.
В одном use case приложение может использовать несколько DAO объектов. Предположим, что в use case идёт работа только с одной базой данных. В соответствии с приведенным выше кодом, каждый DAO объект откроет своё соединения с базой данных. Такая ситуация неприемлема и необходимо, чтобы в рамках use case работа с базой данных шла через одно соединение. Этого можно добиться использованием в приложении Inversion of control фреймворка. С его помощью надо задать параметр времени жизни lifetime для объекта (наследника ADbConnectionManager) соединения с базой данных для веб-приложений как per-request, а для standalone-приложений как singleton.


ссылка на оригинал статьи https://habr.com/ru/articles/928616/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *