Table of Contents
إن نمط العُشب هو أحد أكثر أنماط التصميم الإبداعية استخداما في جافا، وهو يضمن أن يكون للفصل حالة واحدة تماما ويوفر نقطة عالمية للوصول إلى تلك الحالة، وفي التطبيقات المؤسسية، يُستخدم هذا النمط عادة لإدارة وصلات قواعد البيانات - وهو مورد بالغ الأهمية يمكن أن يؤدي، إذا ما أُسيئ التصرف، إلى تدهور الأداء، وإلى تسرب الموارد، وإلى سلوك لا يمكن التنبؤ به، وتتعمق هذه المادة في تنفيذ نهج الربط بين أحزاب متعددة فيما يتعلق بقاعدة البيانات(أ)
لماذا تستخدمين "الخطبة الوحيدة" في عمليات قاعدة البيانات؟
وتُعدّ روابط قواعد البيانات باهظة التكلفة، وكل صلة تستهلك الذاكرة، ومجالس الشبكة، وموارد خواديم قاعدة البيانات، وبدون إدارة دقيقة، يمكن أن يستنفد الطلب بسرعة مجموعة الاتصالات، مما يؤدي إلى انقطاع الوقت والفشل، ويعالج هذا النمط منفردون بضمان وجود وحدة اتصال واحدة في دورة حياة التطبيق، وتشمل الفوائد ما يلي:
- Resource Management:] Limits the number of active connections, reducing overhead on both the client and the database server.
- Consistency:] All components share the same connection, avoiding state divergence caused by multiple connection instances.
- Ease of maintenance:] Centralizes connection creation, formation, and tear down in a single class, streamlineing updates and testing.
- Predictable Life cycle:] The connection is created once and reused, eliminating the need for repeated authentication and handshake overhead.
لكن من المهم ملاحظة أنّه ليس هناك أمان خبز بالخطأ، إذا استخدمت خيوط متعددة نفس الرابط في نفس الوقت، يجب أن تتزامن مع الوصول أو تعتمد على مجموعة اتصال، سنعالج هذه المعاني لاحقاً.
أساسيات "الوحدة الوحيدة" في "جافا"
(الفصل الأول من (سينغلتون لديه ثلاث خصائص أساسية:
- A private constructor] to prevent immediateiation from outside the class.
- A private static variable] that holds the single instance.
- A public static method (often called ) that returns the instance, creating it if necessary.
عندما تُطبّق على وصلات قاعدة البيانات، يُدير صفّ العازب أيضاً موضوع الاتصال ويوفّر طريقة لإسترجاعه (مثلاً، ] دعنا نبدأ بأكثر الطرق تنفّذاً ثم نُحسنه من أجل السلامة.
(مفتاح (نون - خيط - ساف)
إن التنفيذ الأكثر وضوحا هو الاستهلالية المتلهفة، حيث تنشأ الحالة عندما يتم تحميل الصف، وهذا يتفادى التزامن تماما، ولكن قد يضيع الموارد إذا لم تستخدم الصلة أبدا.
public class DatabaseConnection {
private static final DatabaseConnection INSTANCE = new DatabaseConnection();
private final Connection connection;
private DatabaseConnection() {
this.connection = createConnection();
}
public static DatabaseConnection getInstance() {
return INSTANCE;
}
public Connection getConnection() {
return connection;
}
private Connection createConnection() {
// JDBC connection logic (simplified)
try {
Class.forName("com.mysql.cj.jdbc.Driver");
return DriverManager.getConnection(
"jdbc:mysql://localhost:3306/mydb", "user", "password");
} catch (ClassNotFoundException | SQLException e) {
throw new RuntimeException("Failed to create database connection", e);
}
}
}
هذا النهج الكسول ليس آمناً بسبب خيوطين يمكن أن يرىا في وقت واحد وكل منهما ينشئ حالة جديدة، وبالنسبة لنظم الإنتاج، تحتاج إلى السلامة، ثم نستكشف ثلاثة عمليات تنفيذ قوية.
تنفيذات صوفية - سايف
1 - المنهجية المتسقة
فإضافة كلمة مفتاح إلى طريقة تضمن أن يكون هناك خيط واحد فقط يمكن أن ينفذه في وقت ما، وهذا بسيط ولكنه يفرض عقوبة على الأداء لأن كل دعوة إلى تكتسب قفلاً، حتى بعد إنشاء الحالة، ويمكن أن يكون هذا النفق كبيراً بالنسبة لعلاقة قاعدة بيانات يجري الوصول إليها في كثير من الأحيان.
public class DatabaseConnection {
private static DatabaseConnection instance;
private Connection connection;
private DatabaseConnection() {
connection = createConnection();
}
public static synchronized DatabaseConnection getInstance() {
if (instance == null) {
instance = new DatabaseConnection();
}
return instance;
}
public synchronized Connection getConnection() {
return connection;
}
private Connection createConnection() { /* ... */ }
}
ملاحظة أنه حتى بعد الاستهلالية، كل نداء إلى يتطلب التزامن، هذه الطريقة مقبولة فقط إذا تم إنشاء العازب نادرا جدا، ولكن بالنسبة لوصلات قواعد البيانات فمن الأفضل استخدام أحد النهج التالية.
2 - القفل المزدوج الشيك
ويقلل القانون الجنائي من تزامن النفقات العامة عن طريق التحقق أولا من متغير الحالة دون التزامن، ولا يتزامن إلا عندما يكون الوضع في الواقع ، غير أنه من الصعب بشكل ملحوظ تنفيذ المتغيرات الرئيسية التي تُبنى من جانب الطائفة الجافاية القديمة، وذلك بسبب نموذج جافا التذكاري، وبما أن جافا 5 تستخدم مادة تكفل كلمات رئيسية عدم قراءة المتغير من ذاكرة الرئيسية.
public class DatabaseConnection {
private static volatile DatabaseConnection instance;
private Connection connection;
private DatabaseConnection() {
connection = createConnection();
}
public static DatabaseConnection getInstance() {
if (instance == null) {
synchronized (DatabaseConnection.class) {
if (instance == null) {
instance = new DatabaseConnection();
}
}
}
return instance;
}
public Connection getConnection() {
return connection;
}
private Connection createConnection() { /* ... */ }
}
وهذا النمط مستخدم على نطاق واسع ويوفر أداءً جيداً لأن التزامن لا يحدث إلا مرة واحدة، غير أن كلمة مفتاح لها رأس عام على حاجز الذاكرة، وهو أمر لا يذكر بالنسبة لمعظم التطبيقات، وبالنسبة إلى أقصى درجة من السلامة، يفضل العديد من مطوري جافا النهج التالي.
3 - بيل بوغ سينغلتون (إبداء - بناء على طلب ديماند هولدر)
وتستخدم هذه التقنية طبقة داخلية ثابتة خاصة تحمل درجة واحدة، ولا يتم تحميل الصف الداخلي إلا بعد أن يُسمى ]، مما يجعل الاستهلالية كسولة، ويُعتبر السلامة من الخيوط بالتصميم لأن نظام تقييم الأداء المشترك يضمن أن عملية الاستدلال من الدرجة الأولى هي عملية متسلسلة، ولا حاجة إلى التزامن الصريح أو .
public class DatabaseConnection {
private Connection connection;
private DatabaseConnection() {
connection = createConnection();
}
private static class Holder {
static final DatabaseConnection INSTANCE = new DatabaseConnection();
}
public static DatabaseConnection getInstance() {
return Holder.INSTANCE;
}
public Connection getConnection() {
return connection;
}
private Connection createConnection() {
// JDBC setup
try {
Class.forName("com.mysql.cj.jdbc.Driver");
return DriverManager.getConnection(
"jdbc:mysql://localhost:3306/mydb", "user", "password");
} catch (ClassNotFoundException | SQLException e) {
throw new RuntimeException("Failed to create database connection", e);
}
}
}
ويوصى بهذا النمط بالنسبة لمعظم السيناريوهات لأنه يجمع بين الاستبدال الكسول وبين الأمان المضمون للخط الصفري والنفقة العامة للتزامن مع ذلك، كما أنه يعمل بشكل صحيح مع التسلسل إذا نفذت .
4 - Enum sington
(جوشوا بلوك) يدافع عن شخص مُقرّب في كتابه () (الوحدة الوحيدة) (الوحدة)) (الإنجيلية)) (الإنجيل) هو أمر قابل للتسلسل، ويوفر الحماية من الهجمات التأملية، كما أنّها مُهدّدة وكسولة ضمناً، غير أنّ استخدام غطس لربط قاعدة البيانات يتطلب إقامة وصلة دائمة داخل
public enum DatabaseConnection {
INSTANCE;
private Connection connection;
DatabaseConnection() {
connection = createConnection();
}
public Connection getConnection() {
return connection;
}
private Connection createConnection() {
// JDBC setup
try {
Class.forName("com.mysql.cj.jdbc.Driver");
return DriverManager.getConnection(
"jdbc:mysql://localhost:3306/mydb", "user", "password");
} catch (ClassNotFoundException | SQLException e) {
throw new RuntimeException("Failed to create database connection", e);
}
}
}
الاستخدام: [(FLT:20]) النهج الجامح هو نهج مقتضب وقائم، وإن كان يحمّل الصلة بمجرد الإشارة إلى ، وهذا أمر مقبول بالنسبة للعديد من الطلبات.
تركيبة ربط: بديل أفضل؟
وفي حين أن نمط العنبر الواحد مناسب للحد من وجود صلة واحدة، فإن معظم التطبيقات في العالم الحقيقي تتطلب تجميعاً للوصلات ].() ويقيم مجمع للوصلات مجموعة من الصلات التي يمكن استعارتها وإعادتها، مما يوفر إمكانية أفضل للتصعيد واستخدام الموارد، ولا يزال من الممكن استخدام نمط العنبل لإدارة المجمع نفسه - في كثير من الأحيان كطائرة واحدة - ولكن الروابط الفردية تجمعت.
وتشمل عمليات تنفيذ مجموعة الاتصالات الشعبية HikariCP]، Apache DBCP ]، وVibur DCP . Using a singleton to hold a backed by a example.
public class DataSourceSingleton {
private static final HikariConfig config = new HikariConfig();
private static final HikariDataSource dataSource;
static {
config.setJdbcUrl("jdbc:mysql://localhost:3306/mydb");
config.setUsername("user");
config.setPassword("password");
config.setMaximumPoolSize(10);
config.setConnectionTimeout(30000);
dataSource = new HikariDataSource(config);
}
private DataSourceSingleton() {}
public static DataSource getDataSource() {
return dataSource;
}
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
}
هذا العازب يوفر شبكة عالمية وعندما تحتاج إلى اتصال، تدعو ، التي تستعيض عن المجمع، وبعد استخدامه، تغلق الاتصال (الذي يعيده إلى المجمع)، وهذا أمر أكثر تعقيدا من مجرد صلة واحدة، ولا تزال تستفيد من الإدارة المركزية.
عندما تستخدم (سينغلتون) ضد (بولينغ)
- Single connection: ] Acceptable only for embedded databases (e.g., H2 in-memory), very low-traffic applications, or when the database is a simple key-value store and transactions are not concurrent. It's risky for production web applications.
- Singleton instance of a pool:] The recommended approach for multi-threaded applications. The pool is a singleton, but connections are multiple. Use singleton pattern to hold the .
أفضل الممارسات والنظر
- Thread Safety: ] دائما تجعل من الخيط الخاص بك واحدتون، استخدام أيديو أو إيقاع بيل بيو للحائز في معظم الحالات.
- Connection Configuration:] Externalize database accreditation. Use environment variables, formation files, or secrets management. never hardcode accreditation in source code.
- Resource Cleanup:] In a single connection singleton, you must ensure the connection is closed when the application shuts down. Implement a shutdown hook or use a container-managed life cycle.
- Error Recovery:] Database connections can drop.
- Serialization:] If your application sequenceizes the singleton (e.g., in a distributed environment), implement to return the existing instance. The enum approach avoids this issue entirely.
- Reflection Attack:] The reflection API can call private constructors. To prevent this, you can throw an exception in the constructor if the instance already exists. The enum approach is immune.
الروايات المشتركة
- Using a single connection in a multi-threaded web application:] This leads to thread contention and poor performance. instead, always use a connection pool.
- Forgetting to close connections:] With a singleton connection, you might keep it open forever. always provide a method to close it Gracely when the application stops.
- Improper double- checked locking without volatile:] In older Java versions, this can cause subtle fines. Use the holder idiom or enum to avoid complexity.
- أمر الاستبدالية الثابتة: ] If the singleton class references other static fields that are not yet initialized, you may face circular dependencies. Keep initialization simple.
- testing difficulties:] singletons can make unit testing hard because they carry global state. Use dependency injection to pass a connection or datasource, and mock the singleton for tests. alternatively, consider using a test-friendly pattern like the Factory pattern combined with dependency injection.
معرض العالم الحقيقي مع مجمع "كونيشن"
ولتوضيح نهج قراءته للإنتاج، هنا هو واحد يدير مجمع اتصال تابع للشركة باستخدام إيديوه بيل بيو للحائز:
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import java.sql.Connection;
import java.sql.SQLException;
public class ConnectionPoolManager {
private final HikariDataSource dataSource;
private ConnectionPoolManager() {
HikariConfig config = new HikariConfig();
config.setJdbcUrl(System.getenv("DB_URL"));
config.setUsername(System.getenv("DB_USER"));
config.setPassword(System.getenv("DB_PASS"));
config.setMaximumPoolSize(10);
config.setMinimumIdle(2);
config.setConnectionTimeout(5000);
config.setIdleTimeout(300000);
config.setMaxLifetime(600000);
config.setPoolName("MyAppPool");
config.addDataSourceProperty("cachePrepStmts", "true");
config.addDataSourceProperty("prepStmtCacheSize", "250");
config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
dataSource = new HikariDataSource(config);
}
private static class Holder {
static final ConnectionPoolManager INSTANCE = new ConnectionPoolManager();
}
public static ConnectionPoolManager getInstance() {
return Holder.INSTANCE;
}
public Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
public void shutdown() {
if (dataSource != null && !dataSource.isClosed()) {
dataSource.close();
}
}
// Optional: shutdown hook
public void registerShutdownHook() {
Runtime.getRuntime().addShutdownHook(new Thread(this::shutdown));
}
}
Usage:
try (Connection conn = ConnectionPoolManager.getInstance().getConnection()) {
// use connection
} catch (SQLException e) {
// handle
}
هذا النمط قوي، ومأمون، ومختبر للإنتاج، وهو يفصل التشكيل عن الشفرة ويضمن إعادة استخدام الاتصالات بكفاءة.
اختبار قاعدة بيانات سينغلتون
يمكن أن يكون هناك تحدي في قانون الاختبار الذي يعتمد على واحدتون لأن العازب يحتفظ بالوضع عبر الاختبارات، وللتغلب على ذلك، النظر في الاستراتيجيات التالية:
- استخدام واجهة: ] Define an interface for your connection provider, then have the singleton implement it. In tests, you can substitute a mock implementation.
- Reset the singleton:] Add a package-private or protected method to reset the instance (only for testing). Use reflection if needed, but be aware of thread safety.
- Use dependency injection:] instead of calling ] directly, inject the instance via a constructor or setter. This decouples the code and makes testing straightforward.
public class UserService {
private final Connection connection;
public UserService(Connection connection) {
this.connection = connection;
}
// business methods
}
في الإنتاج، ستتصلين بـ في الاختبارات، يمكنكِ أن تمري بعلاقة مُهزّمة
خاتمة
إن نمط العازل هو أداة قيمة لإدارة وصلات قواعد البيانات في جافا، ولكن يجب تنفيذه بحذر من سلامة الخيوط وإدارة الموارد، وبالنسبة لمعظم التطبيقات، فإن مجموعة وصل ملفوفة من طرف واحد توفر أفضل توازن في الأداء والموثوقية، واختيار إيديو أو كومة بيل بيو للحامل على بساطة وسلامة، ورسم شكل خارجي دائما، وتذكر أن وجود صلة واحدة في نظام الإدارة متعدد المستويات نادرا ما يكون مناسبا للإنتاج.
لمزيد من القراءة، استشارة المسؤول أوراكل JDBC tutorial و]HikariCP documentation .] For a deep dive into Java concurrency and the sington pattern, refer to Baeldung' comprehensive guide