connection pool la gi

Bài ghi chép được sự được chấp nhận của người sáng tác Giang Phan

Thiết lập liên kết hạ tầng tài liệu là một trong những quy trình đặc biệt tốn khoáng sản và yên cầu nhiều ngân sách. Hơn nữa, vô một môi trường xung quanh nhiều luồng, việc hé và đóng góp nhiều liên kết thông thường xuyên và liên tiếp tác động thật nhiều cho tới performance và khoáng sản của phần mềm. Trong bài xích này, tôi tiếp tục reviews với chúng ta Connection Pool và cơ hội dùng JDBC Connection Pool vô phần mềm Java.

Bạn đang xem: connection pool la gi

Connection Pooling

Connection Pooling là gì?

Connection pool (vùng kết nối) : là nghệ thuật được chấp nhận tạo ra và giữ lại 1 luyện những liên kết sử dụng công cộng nhằm mục tiêu tăng hiệu suất cho những phần mềm bằng phương pháp dùng lại những liên kết khi đem đòi hỏi chứ không việc tạo ra liên kết mới mẻ.

Xem thêm thắt nhiều việc thực hiện Java lương bổng cao trên TopDev

Cách thao tác của Connection pooling?

Connection Pool Manager (CPM) là trình quản lý và vận hành vùng liên kết, một khi phần mềm được chạy thì Connection pool dẫn đến một vùng liên kết, vô vùng liên kết ê đem những liên kết tự tất cả chúng ta dẫn đến sẵn. Và như thế, một khi mang 1 request cho tới thì CPM đánh giá coi đem liên kết này đang được rỗi không? Nếu đem nó sẽ bị sử dụng liên kết này còn ko thì nó sẽ bị đợi cho tới khi đem liên kết này ê rỗi hoặc liên kết không giống bị timeout. Kết nối sau thời điểm dùng sẽ không còn đóng góp lại tức thì tuy nhiên sẽ tiến hành trả về CPM nhằm sử dụng lại khi được đòi hỏi vô sau này.

Ví dụ: 

Một connection pool đem tối nhiều 10 connection vô pool. Bây giờ user liên kết cho tới database (DB), khối hệ thống tiếp tục đánh giá vô connection pool đem liên kết này đang được rảnh không?

  • Trường ăn ý chưa tồn tại liên kết này vô connection pool hoặc toàn bộ những liên kết đều bận (đang được dùng vị user khác) và con số connection vô connection < 10 thì sẽ khởi tạo một connection mới mẻ cho tới DB nhằm liên kết cho tới DB đôi khi liên kết ê sẽ tiến hành tiến hành connection pool.
  • Trường ăn ý toàn bộ những liên kết đang được bận và con số connection vô connection pool = 10 thì người tiêu dùng nên đợi cho những user sử dụng xong xuôi nhằm được sử dụng.

Sau khi một liên kết được tạo ra và dùng xong xuôi nó sẽ không còn đóng góp lại tuy nhiên tiếp tục giữ lại vô connection pool nhằm sử dụng lại mang lại phen sau và chỉ thực sự bị đóng góp khi không còn thời hạn timeout (lâu quá ko sử dụng cho tới nữa).

Giới thiệu JDBC Connection Pool

JDBC Connection Pooling

Giới thiệu JDBC Connection Pool

Cơ chế thao tác của JDBC Connection Pooling tuân hành theo dõi Connection Pooling. Sau khi phần mềm được start, một Connection Pool object được tạo ra, những Connection object sau đây được tạo ra sẽ tiến hành quản lý và vận hành vị pool này.

Có nhiều tủ sách tương hỗ JDBC Conection Pooling như: C3P0, Apache Commons DBCP, HikariCP, … Chúng tao tiếp tục thứu tự mò mẫm hiểu những setup và dùng bọn chúng vô phần tiếp sau của nội dung bài viết này.

Cấu hình Maven Dependencies

Tạo maven project và thêm thắt những maven dependencies vô file pom.xml:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">     <modelVersion>4.0.0</modelVersion>     <groupId>com.gpcoder</groupId>     <artifactId>JdbcExample</artifactId>     <version>0.0.1-SNAPSHOT</version>     <packaging>jar</packaging>     <name>JdbcExample</name>     <url>http://maven.apache.org</url>     <properties>         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>         <maven.compiler.source>1.8</maven.compiler.source>         <maven.compiler.target>1.8</maven.compiler.target>     </properties>     <dependencies>                  <dependency>             <groupId>mysql</groupId>             <artifactId>mysql-connector-java</artifactId>             <version>8.0.17</version>         </dependency>                  <dependency>             <groupId>org.projectlombok</groupId>             <artifactId>lombok</artifactId>             <version>1.18.10</version>         </dependency>                                    <dependency>             <groupId>com.mchange</groupId>             <artifactId>c3p0</artifactId>             <version>0.9.5.4</version>         </dependency>                           <dependency>             <groupId>org.apache.commons</groupId>             <artifactId>commons-dbcp2</artifactId>             <version>2.7.0</version>         </dependency>                           <dependency>             <groupId>com.zaxxer</groupId>             <artifactId>HikariCP</artifactId>             <version>3.4.0</version>         </dependency>     </dependencies> </project>

Lưu ý: chúng ta chỉ việc dùng 1 trong số tủ sách Conection Pooling bên trên, không cần thiết phải thêm thắt toàn bộ. Trong bài xích này bản thân chỉ dẫn dùng toàn bộ tủ sách ê nên cần thiết thêm thắt toàn bộ.

Sử dụng JDBC connection pool

Đầu tiên, cần thiết tạo ra class constant chứa chấp vấn đề thông số kỹ thuật liên kết database, con số connection ít nhất, tối nhiều vô Pool.

DbConfiguration.java

package com.gpcoder.constants;

public class DbConfiguration {

public static final String HOST_NAME = "localhost";
public static final String DB_NAME = "jdbcdemo";
public static final String DB_PORT = "3306";
public static final String USER_NAME = "root";
public static final String PASSWORD = "";
public static final String DB_DRIVER = "com.mysql.cj.jdbc.Driver";
public static final int DB_MIN_CONNECTIONS = 2;
public static final int DB_MAX_CONNECTIONS = 4;
// jdbc:mysql://hostname:port/dbname
public static final String CONNECTION_URL = "jdbc:mysql://" + HOST_NAME + ":" + DB_PORT + "/" + DB_NAME;

private DbConfiguration() {
super();
}
}

c3p0

Khởi tạo ra Connection Pool dùng C3p0 và hỗ trợ công thức nhằm get Connection. Trong class này tôi tiếp tục thêm thắt một trong những log nhằm chúng ta theo dõi dõi cơ hội Connection Pool sinh hoạt.

Xem thêm: độc giả cùng tác giả đồng thời xuyên vào sách

C3p0DataSource.java

package com.gpcoder.pool.thirdparties;

import java.beans.PropertyVetoException;
import java.sql.Connection;
import java.sql.SQLException;

import com.gpcoder.constants.DbConfiguration;
import com.mchange.v2.c3p0.ComboPooledDataSource;

public class C3p0DataSource {

private static ComboPooledDataSource cpds = new ComboPooledDataSource();

static {
try {
cpds.setDriverClass(DbConfiguration.DB_DRIVER);
cpds.setJdbcUrl(DbConfiguration.CONNECTION_URL);
cpds.setUser(DbConfiguration.USER_NAME);
cpds.setPassword(DbConfiguration.PASSWORD);
cpds.setMinPoolSize(DbConfiguration.DB_MIN_CONNECTIONS);
cpds.setInitialPoolSize(DbConfiguration.DB_MIN_CONNECTIONS);
cpds.setMaxPoolSize(DbConfiguration.DB_MAX_CONNECTIONS);
} catch (PropertyVetoException e) {
e.printStackTrace();
}
}

private C3p0DataSource() {
super();
}

public static Connection getConnection(String task) throws SQLException {
System.out.println("Getting connection for task " + task);
Connection conn = cpds.getConnection();
logPoolStatus(task);
return conn;
}

public synchronized static void logPoolStatus(String task) throws SQLException {
System.out.println("Received connection for task " + task);
System.out.println("+ Num of Connections: " + cpds.getNumConnections());
System.out.println("+ Num of Idle Connections: " + cpds.getNumIdleConnections());
System.out.println("+ Num of Busy Connections: " + cpds.getNumBusyConnections());
}
}

C3p0ConnectionPoolingExample.java

package com.gpcoder.pool;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.concurrent.CountDownLatch;

import com.gpcoder.pool.thirdparties.C3p0DataSource;

class C3p0WorkerThread extends Thread {

private String taskName;
private CountDownLatch latch;

public C3p0WorkerThread(CountDownLatch latch, String taskName) {
this.taskName = taskName;
this.latch = latch;
}

@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " Starting. Task = " + taskName);
execute();
latch.countDown();
System.out.println(Thread.currentThread().getName() + " Finished.");
}

private void execute() {
try {
String sqlSelect = "SELECT COUNT(*) AS total FROM user";
try (Connection con cái = C3p0DataSource.getConnection(taskName);
Statement st = con cái.createStatement();
ResultSet rs = st.executeQuery(sqlSelect);) {
Thread.sleep(2000);
rs.next();
System.out.println("Task = " + taskName + ": Run SQL successfully " + rs.getInt("total"));
}
} catch (SQLException | InterruptedException e) {
e.printStackTrace();
}
}
}

public class C3p0ConnectionPoolingExample {

private static final int NUMBER_OF_USERS = 8;

public static void main(String[] args) throws SQLException, InterruptedException {
final CountDownLatch latch = new CountDownLatch(NUMBER_OF_USERS);
for (int i = 1; i <= NUMBER_OF_USERS; i++) {
Thread worker = new C3p0WorkerThread(latch, "" + i);
worker.start();
}
latch.await();
System.out.println("DONE All Tasks");
C3p0DataSource.logPoolStatus("Main");
}
}

Trong công tác bên trên, tôi tạo ra 8 Thread không giống nhau truy vấn data nằm trong 1 thời điểm, Connection Pool của tất cả chúng ta ban sơ khởi tạo ra là 2 Connection, tối nhiều đem 4 Connection và từng Thread dùng Connection vô 2 giây.

Chạy công tác bên trên, tất cả chúng ta đem thành quả sau:

Thread-0 Starting. Task = 1
Thread-1 Starting. Task = 2
Thread-3 Starting. Task = 4
Thread-2 Starting. Task = 3
Thread-4 Starting. Task = 5
Thread-5 Starting. Task = 6
Thread-6 Starting. Task = 7
Thread-7 Starting. Task = 8
Getting connection for task 1
Getting connection for task 8
Getting connection for task 7
Getting connection for task 6
Getting connection for task 5
Getting connection for task 3
Getting connection for task 4
Getting connection for task 2
Received connection for task 2
+ Num of Connections: 3
+ Num of Idle Connections: 1
+ Num of Busy Connections: 2
Received connection for task 8
+ Num of Connections: 3
+ Num of Idle Connections: 0
+ Num of Busy Connections: 3
Received connection for task 1
+ Num of Connections: 3
+ Num of Idle Connections: 0
+ Num of Busy Connections: 3
Received connection for task 4
+ Num of Connections: 4
+ Num of Idle Connections: 0
+ Num of Busy Connections: 4
Task = 8: Run SQL successfully 15
Task = 4: Run SQL successfully 15
Task = 2: Run SQL successfully 15
Thread-7 Finished.
Thread-1 Finished.
Thread-3 Finished.
Received connection for task 6
+ Num of Connections: 4
+ Num of Idle Connections: 1
+ Num of Busy Connections: 3
Received connection for task 7
+ Num of Connections: 4
+ Num of Idle Connections: 0
+ Num of Busy Connections: 4
Received connection for task 5
+ Num of Connections: 4
+ Num of Idle Connections: 0
+ Num of Busy Connections: 4
Task = 1: Run SQL successfully 15
Thread-0 Finished.
Received connection for task 3
+ Num of Connections: 4
+ Num of Idle Connections: 0
+ Num of Busy Connections: 4
Task = 6: Run SQL successfully 15
Thread-5 Finished.
Task = 7: Run SQL successfully 15
Thread-6 Finished.
Task = 3: Run SQL successfully 15
Task = 5: Run SQL successfully 15
Thread-2 Finished.
Thread-4 Finished.
DONE All Tasks
Received connection for task Main
+ Num of Connections: 4
+ Num of Idle Connections: 4
+ Num of Busy Connections: 0

Như chúng ta thấy, bên trên một thười lăn tay rất có thể tối nhiều 4 Connection được xử lý, khi một Connection rảnh (idle) tiếp tục rất có thể xử lý tiếp một đòi hỏi Connection không giống. Với phương thức này, tất cả chúng ta rất có thể quản lý và vận hành được con số Connection rất có thể hé đôi khi nhằm đáp ứng vô phần mềm.

Apache Commons DBCP

Tương tự động như C3P0, tất cả chúng ta tạo ra class Connection Pool như sau:

package com.gpcoder.pool.thirdparties;

import java.sql.Connection;
import java.sql.SQLException;

import org.apache.commons.dbcp2.BasicDataSource;

import com.gpcoder.constants.DbConfiguration;

public class DBCPDataSource {

private static BasicDataSource ds = new BasicDataSource();

static {
ds.setDriverClassName(DbConfiguration.DB_DRIVER);
ds.setUrl(DbConfiguration.CONNECTION_URL);
ds.setUsername(DbConfiguration.USER_NAME);
ds.setPassword(DbConfiguration.PASSWORD);
ds.setMinIdle(DbConfiguration.DB_MIN_CONNECTIONS); // minimum number of idle connections in the pool
ds.setInitialSize(DbConfiguration.DB_MIN_CONNECTIONS);
ds.setMaxIdle(DbConfiguration.DB_MAX_CONNECTIONS); // maximum number of idle connections in the pool
ds.setMaxOpenPreparedStatements(100);
}

private DBCPDataSource() {
super();
}

public static Connection getConnection() throws SQLException {
return ds.getConnection();
}
}

DBCPConnectionPoolingExample.java

package com.gpcoder.pool;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.concurrent.CountDownLatch;

import com.gpcoder.pool.thirdparties.DBCPDataSource;

class DBCPWorkerThread extends Thread {

private CountDownLatch latch;
private String taskName;

public DBCPWorkerThread(CountDownLatch latch, String taskName) {
this.latch = latch;
this.taskName = taskName;
}

@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " Starting. Task = " + taskName);
execute();
latch.countDown();
System.out.println(Thread.currentThread().getName() + " Finished.");
}

private void execute() {
try {
String sqlSelect = "SELECT COUNT(*) AS total FROM user";
try (Connection conn = DBCPDataSource.getConnection();
Statement st = conn.createStatement();
ResultSet rs = st.executeQuery(sqlSelect);) {
Thread.sleep(2000);
rs.next();
System.out.println("Task = " + taskName + ": Run SQL successfully " + rs.getInt("total"));
}
} catch (SQLException | InterruptedException e) {
e.printStackTrace();
}
}
}

public class DBCPConnectionPoolingExample {

private static final int NUMBER_OF_USERS = 8;

public static void main(String[] args) throws SQLException, InterruptedException {
final CountDownLatch latch = new CountDownLatch(NUMBER_OF_USERS);
for (int i = 1; i <= NUMBER_OF_USERS; i++) {
Thread worker = new DBCPWorkerThread(latch, "" + i);
worker.start();
}
latch.await();
System.out.println("DONE All Tasks");
}
}

Hikari

package com.gpcoder.pool.thirdparties;

import java.sql.Connection;
import java.sql.SQLException;

import com.gpcoder.constants.DbConfiguration;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;

public class HikariCPDataSource {

private static HikariConfig config = new HikariConfig();

private static HikariDataSource ds;

static {
config.setDriverClassName(DbConfiguration.DB_DRIVER);
config.setJdbcUrl(DbConfiguration.CONNECTION_URL);
config.setUsername(DbConfiguration.USER_NAME);
config.setPassword(DbConfiguration.PASSWORD);
config.setMinimumIdle(DbConfiguration.DB_MIN_CONNECTIONS);
config.setMaximumPoolSize(DbConfiguration.DB_MAX_CONNECTIONS);
// Some additional properties
config.addDataSourceProperty("cachePrepStmts", "true");
config.addDataSourceProperty("prepStmtCacheSize", "250");
config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
ds = new HikariDataSource(config);
}

private HikariCPDataSource() {
super();
}

public static Connection getConnection() throws SQLException {
return ds.getConnection();
}
}

HikariConnectionPoolingExample.java

package com.gpcoder.pool;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.concurrent.CountDownLatch;

import com.gpcoder.pool.thirdparties.HikariCPDataSource;

class HikariWorkerThread extends Thread {

private CountDownLatch latch;
private String taskName;

public HikariWorkerThread(CountDownLatch latch, String taskName) {
this.latch = latch;
this.taskName = taskName;
}

@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " Starting. Task = " + taskName);
execute();
latch.countDown();
System.out.println(Thread.currentThread().getName() + " Finished.");
}

private void execute() {
try {
String sqlSelect = "SELECT COUNT(*) AS total FROM user";
try (Connection conn = HikariCPDataSource.getConnection();
Statement st = conn.createStatement();
ResultSet rs = st.executeQuery(sqlSelect);) {
Thread.sleep(2000);
rs.next();
System.out.println("Task = " + taskName + ": Run SQL successfully " + rs.getInt("total"));
}
} catch (SQLException | InterruptedException e) {
e.printStackTrace();
}
}
}

public class HikariConnectionPoolingExample {

private static final int NUMBER_OF_USERS = 8;

public static void main(String[] args) throws SQLException, InterruptedException {
final CountDownLatch latch = new CountDownLatch(NUMBER_OF_USERS);
for (int i = 1; i <= NUMBER_OF_USERS; i++) {
Thread worker = new HikariWorkerThread(latch, "" + i);
worker.start();
}
latch.await();
System.out.println("DONE All Tasks");
}
}

Tự tạo ra Connection Pool

Nếu không thích dùng tủ sách đã có sẵn trước, tất cả chúng ta rất có thể tự động tạo ra một Connection Pool. Tương tự động như sau:

GPConnectionPool.java

package com.gpcoder.pool.custom;

import java.sql.Connection;

public interface GPConnectionPool {

Connection getConnection();

boolean releaseConnection(Connection connection);
}

GPConnectionPoolImpl.java

package com.gpcoder.pool.custom;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.LinkedList;

import com.gpcoder.ConnectionUtils;
import com.gpcoder.constants.DbConfiguration;

public class GPConnectionPoolImpl implements GPConnectionPool {

private static final LinkedList availableConnections = new LinkedList<>();

private int maxConnection;

public GPConnectionPoolImpl(int maxConnection) {
this.maxConnection = maxConnection;
initializeConnectionPool();
}

private synchronized void initializeConnectionPool() {
try {
while (!checkIfConnectionPoolIsFull()) {
Connection newConnection = ConnectionUtils.openConnection();
availableConnections.add(newConnection);
}
notifyAll();
} catch (SQLException e) {
e.printStackTrace();
}
}

private boolean checkIfConnectionPoolIsFull() {
return availableConnections.size() >= maxConnection;
}

@Override
public synchronized Connection getConnection() {
while(availableConnections.size() == 0) {
// Wait for an existing connection vĩ đại be freed up.
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// Retrieves and removes the first element of this list
return availableConnections.poll();
}

@Override
public synchronized boolean releaseConnection(Connection connection) {
try {
if (connection.isClosed()) {
initializeConnectionPool();
} else {
// Adds the specified element as the last element of this list
boolean isReleased = availableConnections.offer(connection);
// Wake up threads that are waiting for a connection
notifyAll();
return isReleased;
}
} catch (SQLException e) {
e.printStackTrace();
}
return false;
}

public synchronized String toString() {
StringBuilder sb = new StringBuilder()
.append("Max=" + DbConfiguration.DB_MAX_CONNECTIONS)
.append(" | Available=" + availableConnections.size())
.append(" | Busy=" + (DbConfiguration.DB_MAX_CONNECTIONS - availableConnections.size()))
;
return sb.toString();
}
}

GPDataSource.java

package com.gpcoder.pool.custom;

import java.sql.Connection;

import com.gpcoder.constants.DbConfiguration;

public class GPDataSource {

private static final GPConnectionPool gpPool = new GPConnectionPoolImpl(DbConfiguration.DB_MAX_CONNECTIONS);

private GPDataSource() {
super();
}

public static Connection getConnection() {
Connection connection = gpPool.getConnection();
System.out.println("GPPool status: " + gpPool);
return connection;
}

public static boolean releaseConnection(Connection connection) {
return gpPool.releaseConnection(connection);
}
}

GPConnectionPoolExample.java

package com.gpcoder.pool;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.concurrent.CountDownLatch;

import com.gpcoder.pool.custom.GPDataSource;

class GPWorkerThread extends Thread {

private String taskName;
private CountDownLatch latch;

public GPWorkerThread(CountDownLatch latch, String taskName) {
this.taskName = taskName;
this.latch = latch;
}

@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " Starting. Task = " + taskName);
execute();
latch.countDown();
System.out.println(Thread.currentThread().getName() + " Finished.");
}

private void execute() {
try {
String sqlSelect = "SELECT COUNT(*) AS total FROM user";
Connection connection = GPDataSource.getConnection();
try (Statement st = connection.createStatement();
ResultSet rs = st.executeQuery(sqlSelect);) {
Thread.sleep(2000);
rs.next();
System.out.println("Task = " + taskName + ": Run SQL successfully " + rs.getInt("total"));
}
GPDataSource.releaseConnection(connection);
} catch (SQLException | InterruptedException e) {
e.printStackTrace();
}
}
}

public class GPConnectionPoolExample {

private static final int NUMBER_OF_USERS = 8;

public static void main(String[] args) throws SQLException, InterruptedException {
final CountDownLatch latch = new CountDownLatch(NUMBER_OF_USERS);
for (int i = 1; i <= NUMBER_OF_USERS; i++) {
Thread worker = new GPWorkerThread(latch, "" + i);
worker.start();
}
latch.await();
System.out.println("DONE All Tasks");
}
}

Output chương trình:

Xem thêm: tung hoành cổ đại

Thread-0 Starting. Task = 1
Thread-1 Starting. Task = 2
Thread-2 Starting. Task = 3
Thread-3 Starting. Task = 4
Thread-4 Starting. Task = 5
Thread-5 Starting. Task = 6
Thread-6 Starting. Task = 7
Thread-7 Starting. Task = 8
GPPool status: Max=4 | Available=2 | Busy=2
GPPool status: Max=4 | Available=1 | Busy=3
GPPool status: Max=4 | Available=0 | Busy=4
GPPool status: Max=4 | Available=2 | Busy=2
Task = 7: Run SQL successfully 15
Task = 1: Run SQL successfully 15
Task = 8: Run SQL successfully 15
GPPool status: Max=4 | Available=0 | Busy=4
GPPool status: Max=4 | Available=0 | Busy=4
Task = 6: Run SQL successfully 15
Thread-5 Finished.
Thread-7 Finished.
GPPool status: Max=4 | Available=1 | Busy=3
Thread-6 Finished.
Thread-0 Finished.
GPPool status: Max=4 | Available=0 | Busy=4
Task = 3: Run SQL successfully 15
Task = 5: Run SQL successfully 15
Thread-2 Finished.
Thread-4 Finished.
Task = 2: Run SQL successfully 15
Task = 4: Run SQL successfully 15
Thread-1 Finished.
Thread-3 Finished.
DONE All Tasks

Bài ghi chép gốc được đăng lên bên trên gpcoder.com

Có thể chúng ta quan tiền tâm:

  • Hibernate Batch processing
  • Giới thiệu về Docker Compose
  • Cài đặt điều và dùng Hibernate

Xem thêm thắt việc thực hiện IT thú vị trên TopDev