Dal is Ctrip’s open-source database access framework, designed to manage large-scale database infrastructure.
On the DB management side, Dal provides a unified data access layer: it supports both Java and C# clients, works with MySQL and SQL Server, handles both ORM and raw SQL access patterns, uses Emit mapping for high-performance ORM, supports multi-datasource configurations with master-slave separation (read-write splitting), and includes built-in logging and monitoring.
On the developer experience side, Dal supports code generation. Through the Dal platform, developers can generate Entity classes, Dao layers, and unit tests with a single click. This frees developers from writing boilerplate DB code and enforces consistent coding standards across teams.
2. The Compatibility Problem
Dal’s core design principle is centralized control. Clients don’t configure database usernames and passwords directly — they use a TitanKey or ClusterName issued by Dal, which acts as the credential for database access. This means DataX, which expects traditional JDBC connection parameters, can’t work out of the box on a Dal-managed system.
Two problems need to be solved: how to configure DataX with Dal’s connection credentials, and how to obtain a DataSource through Dal’s API.
3. Configuring DataX with TitanKey or ClusterName
Here’s the standard mysqlwriter configuration template:
publicfinalclassDBUtil{ privateDBUtil(){ } @Resource private DalDataSourceFactory dsFactory; /** * DataSource factory bean */ @Bean public DalDataSourceFactory getCtripDalDataSource(){ returnnew DalDataSourceFactory(); } /** * Get connection by titanKey */ publicstatic Connection getConnectionByTitanKey(final String titanKey){ try { DataSource dataSource = dsFactory.createDataSource(titanKey); return dataSource.getConnection(); } catch (Exception e) { throw DataXException .asDataXException(DBUtilErrorCode.CONN_DB_ERROR, String.format("Database connection failed. Unable to get connection with config: %s. Please check your configuration.", titanKey), e); } } /** * Get connection by clusterName */ publicstatic Connection getConnectionByClusterName(final String clusterName){ try { DataSource ds = dsFactory.getOrCreateDataSource(clusterName); return dataSource.getConnection(); } catch (Exception e) { throw DataXException .asDataXException(DBUtilErrorCode.CONN_DB_ERROR, String.format("Database connection failed. Unable to get connection with config: %s. Please check your configuration.", clusterName), e); } } }
The solution is straightforward: use Dal’s DataSource factory to create connections, replacing DataX’s default JDBC-based approach.
5. An Optimized Approach
The implementation above has a performance issue — it creates a new DataSource on every sync job. Since Dal already provides a DataSource factory with built-in pooling, we can cache the DataSource instances and reuse them across jobs, using dbName as the cache key.
Here’s the improved approach: load and cache DataSources during application startup.
/** * Cluster name connection info */ publicstaticfinal String CLUSTER_NAME_TEST_DB = "test_cluster_db";
/** * Load DataSource by titan key */ privatevoidfillDataSourceFromTitanKey(String titanKey){ try { Assert.hasText(titanKey, "connect to db failed; titan key cannot be null or empty");
/** * Load DataSource by cluster name */ privatevoidfillDataSourceFromClusterName(String clusterName){ try { Assert.hasText(clusterName, "connect to db failed; dal cluster cannot be null or empty");
// Validate cluster name format Assert.isTrue(clusterName.contains(CLUSTER_CONN_TYPE_FLAG), String.format("%s is not in a cluster format", clusterName));
publicfinalclassDBUtil{ privateDBUtil(){ } privatestaticfinal Logger LOG = LoggerFactory.getLogger(DBUtil.class); privatestaticfinal Map<String, DataSource> DS_MAP = new ConcurrentHashMap<>(); /** * Register a DataSource into the engine cache * * @param dsName DataSource name (used to retrieve the DataSource later) * @param ds DataSource instance */ publicstaticvoidsetDataSourceIfAbsent(String dsName, DataSource ds){ if (DS_MAP.containsKey(dsName)) { return; } synchronized (DS_MAP) { if (!dsMap.containsKey(dsName)) { DS_MAP.put(dsName, ds); LOG.info("setDataSourceIfAbsent registered DataSource: {}", dsName); } } } /** * Get a cached DataSource by name * @param dsName DataSource name * @return DataSource instance */ privatestatic DataSource getDataSource(String dsName){ // Strip query parameters from JDBC URLs if (dsName.contains("?")) { dsName = dsName.substring(0, dsName.indexOf("?")); } return DS_MAP.get(dsName); } /** * Get a database connection from the cached DataSource * @param dsName DataSource name * @return database connection */ publicstatic Connection getConnection(String dsName){ try { // Get the cached DataSource DataSource dataSource = getDataSource(dsName); Assert.notNull(dataSource, String.format("Failed to get DataSource: %s", dsName)); return dataSource.getConnection(); } catch (Exception e) { throw DataXException .asDataXException(DBUtilErrorCode.CONN_DB_ERROR, String.format("Database connection failed. Unable to get connection with config: %s. Please check your configuration.", dsName), e); } } }
This design loads all DataSources once at startup and caches them in a ConcurrentHashMap. Subsequent sync jobs retrieve connections from the cached DataSources by name, avoiding the overhead of recreating DataSource instances on every run.