Слой приложения persistence layer является в определённом смысле уникальным в смысле узкой направленности его функционала по сравнению с другими слоями приложения. Если рассматривать его только для работы с реляционными базами данных, то реализацию функционала слоя можно разбить на два основных варианта — с использованием ORM фреймворка и без использования ORM фреймворка. Каждый из этих вариантов можно реализовать достаточно универсальным образом.
Реализация с использованием ORM фреймворка прекрасно описана в разделах 18.1 и 18.2 в книге Бауэр К., Кинг Г., Грегори Г. Java Persistence API и Hibernate. ДМК Пресс, 2017.
В этой статье рассмотрен пример реализации слоя persistence layer без использования ORM фреймворка. Предлагаемое решение является простым и в тоже время достаточно универсальным для использования в языках программирования, поддерживающих объектную модель.
Структуру слоя persistence layer рассмотрим в виде трёхуровневой иерархии функционала.
Эти уровни иерархии можно рассматривать как подслои persistence layer.
-
Фасад слоя — набор объектов доступа к внешним персистентным данным (DAO объектов). Через фасад происходит доступ к функционалу слоя из вышележащих слоёв приложения. Фасад скрывает детали реализации работы с базой данных от вышележащих слоёв приложения.
-
Механизмы обработки персистентных данных.
-
Механизмы доступа к реляционным базам данных.
Модель данных слоя persistence layer в данном примере представлена классом Factor. Его структура данных соответствует структуре данных в строке таблицы tblFactors в базе данных.
public class Factor { public int Id; public string Name; public decimal Value; }
Рассмотрим примеры кода на C#, который реализует функционал слоя.
-
Объекты доступа к внешним персистентным данным (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:
-
history — база данных телеметрии;
-
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/
Добавить комментарий