main
HuangHai 1 month ago
parent e93223b45b
commit 0e2da03a07

@ -0,0 +1,160 @@
# 数据库读写分离功能说明
## 概述
本项目已集成数据库读写分离功能,通过修改 `Db.java` 类实现了自动的读写分离路由。该功能具有以下特点:
- **自动检测**:根据 `application.yaml` 配置自动检测是否存在从库配置
- **智能路由**:读操作自动路由到从库,写操作强制使用主库
- **负载均衡**:多个从库时随机选择一个进行读取
- **故障转移**:从库连接失败时自动回退到主库
- **零侵入**:现有代码无需修改,透明支持读写分离
## 配置方式
### 1. 主库配置(必须)
`application_dev.yaml``application_pro.yaml` 中配置主库:
```yaml
mysql:
driverClassName: com.mysql.cj.jdbc.Driver
user: root
password: 123456
jdbcUrl: jdbc:mysql://localhost:3306/dswork?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&allowMultiQueries=true
```
### 2. 从库配置(可选)
如果需要启用读写分离,添加从库配置:
```yaml
mysql:
driverClassName: com.mysql.cj.jdbc.Driver
user: root
password: 123456
jdbcUrl: jdbc:mysql://localhost:3306/dswork?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&allowMultiQueries=true
# 从库1
slave1:
driverClassName: com.mysql.cj.jdbc.Driver
user: root
password: 123456
jdbcUrl: jdbc:mysql://slave1:3306/dswork?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&allowMultiQueries=true
# 从库2
slave2:
driverClassName: com.mysql.cj.jdbc.Driver
user: root
password: 123456
jdbcUrl: jdbc:mysql://slave2:3306/dswork?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&allowMultiQueries=true
```
## 使用方式
### 1. 自动读写分离(推荐)
现有代码无需修改,系统会自动根据 SQL 类型进行路由:
```java
// 读操作 - 自动路由到从库
List<Record> users = Db.find("select * from user where status = ?", 1);
Record user = Db.findFirst("select * from user where id = ?", 123);
List<Record> result = Db.query("select count(*) from user");
// 写操作 - 强制使用主库
Db.update("insert into user(name, email) values(?, ?)", "张三", "zhangsan@example.com");
Db.update("update user set status = ? where id = ?", 1, 123);
Db.delete("delete from user where id = ?", 123);
// 事务操作 - 强制使用主库
Db.tx(() -> {
Db.update("insert into user(name) values(?)", "李四");
Db.update("update user set status = 1 where name = ?", "李四");
return true;
});
```
### 2. 强制指定数据库
如果需要强制使用特定数据库,可以使用以下方法:
```java
// 强制使用主库查询(用于需要强一致性的场景)
List<Record> users = Db.queryFromMaster("select * from user where id = ?", 123);
Record user = Db.findFirstFromMaster("select * from user where id = ?", 123);
// 强制使用指定从库查询
List<Record> users = Db.queryFromSlave(0, "select * from user"); // 使用第1个从库
```
### 3. 查看读写分离状态
```java
// 获取读写分离状态信息
String status = Db.getReadWriteSeparationStatus();
System.out.println(status);
```
## 自动路由规则
### 读操作(路由到从库)
以下 SQL 语句会自动路由到从库:
- `SELECT` 查询语句
- `SHOW` 语句
- `DESC` / `DESCRIBE` 语句
- `EXPLAIN` 语句
对应的方法包括:
- `Db.find()`、`Db.findFirst()`、`Db.findById()` 等
- `Db.query()`、`Db.queryFirst()`、`Db.queryXxx()` 等
- `Db.paginate()` 分页查询
- `Db.findByCache()` 缓存查询
### 写操作(强制使用主库)
以下操作强制使用主库:
- `INSERT`、`UPDATE`、`DELETE` 语句
- 所有事务操作
- `Db.save()`、`Db.update()`、`Db.delete()` 等
- `Db.tx()` 事务方法
## 性能优化建议
1. **合理配置从库数量**:根据读写比例配置适当数量的从库
2. **使用连接池**:确保主从库都配置了合适的连接池参数
3. **监控从库延迟**:定期检查主从同步延迟,避免读取到过期数据
4. **强一致性场景**:对于需要强一致性的查询,使用 `queryFromMaster()` 方法
## 注意事项
1. **主从同步延迟**:从库可能存在数据同步延迟,对于实时性要求高的查询建议使用主库
2. **事务一致性**:所有事务操作都在主库执行,确保数据一致性
3. **配置格式**:从库配置必须以 `slave` 开头,后跟数字(如 `slave1`、`slave2`
4. **故障处理**:从库连接失败时会自动回退到主库,不会影响业务正常运行
## 启动日志
系统启动时会输出读写分离状态信息:
```
[Db] 读写分离已启用,发现 2 个从库配置
```
```
[Db] 未发现从库配置,使用单库模式
```
## 故障排查
如果读写分离功能异常,请检查:
1. 配置文件中从库配置格式是否正确
2. 从库连接信息是否正确
3. 从库服务是否正常运行
4. 网络连接是否正常
系统会在控制台输出相关错误信息,便于排查问题。

@ -0,0 +1,872 @@
/**
* Copyright (c) 2011-2025, James Zhan (jfinal@126.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jfinal.plugin.activerecord;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Connection;
import java.sql.SQLException;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Future;
import java.util.function.Function;
import com.jfinal.kit.SyncWriteMap;
/**
* Db. Powerful database query and update tool box.
*/
@SuppressWarnings("rawtypes")
public class Db {
private static DbPro MAIN = null;
private static final Map<String, DbPro> cache = new SyncWriteMap<>(32, 0.25F);
/**
* for DbKit.addConfig(configName)
*/
static void init(String configName) {
MAIN = DbKit.getConfig(configName).dbProFactory.getDbPro(configName); // new DbPro(configName);
cache.put(configName, MAIN);
}
/**
* for DbKit.removeConfig(configName)
*/
static void removeDbProWithConfig(String configName) {
if (MAIN != null && MAIN.config.getName().equals(configName)) {
MAIN = null;
}
cache.remove(configName);
}
public static DbPro use(String configName) {
DbPro result = cache.get(configName);
if (result == null) {
Config config = DbKit.getConfig(configName);
if (config == null) {
throw new IllegalArgumentException("Config not found by configName: " + configName);
}
result = config.dbProFactory.getDbPro(configName); // new DbPro(configName);
cache.put(configName, result);
}
return result;
}
public static DbPro use() {
return MAIN;
}
static <T> List<T> query(Config config, Connection conn, String sql, Object... paras) throws SQLException {
return MAIN.query(config, conn, sql, paras);
}
/**
* sql paras JDBC Record
*/
public static <T> List<T> query(String sql, Object... paras) {
return MAIN.query(sql, paras);
}
/**
* @see #query(String, Object...)
* @param sql an SQL statement
*/
public static <T> List<T> query(String sql) {
return MAIN.query(sql);
}
/**
* Execute sql query and return the first result. I recommend add "limit 1" in your sql.
* @param sql an SQL statement that may contain one or more '?' IN parameter placeholders
* @param paras the parameters of sql
* @return Object[] if your sql has select more than one column,
* and it return Object if your sql has select only one column.
*/
public static <T> T queryFirst(String sql, Object... paras) {
return MAIN.queryFirst(sql, paras);
}
/**
* @see #queryFirst(String, Object...)
* @param sql an SQL statement
*/
public static <T> T queryFirst(String sql) {
return MAIN.queryFirst(sql);
}
// 26 queryXxx method below -----------------------------------------------
/**
* Execute sql query just return one column.
* @param <T> the type of the column that in your sql's select statement
* @param sql an SQL statement that may contain one or more '?' IN parameter placeholders
* @param paras the parameters of sql
* @return <T> T
*/
public static <T> T queryColumn(String sql, Object... paras) {
return MAIN.queryColumn(sql, paras);
}
public static <T> T queryColumn(String sql) {
return MAIN.queryColumn(sql);
}
public static String queryStr(String sql, Object... paras) {
return MAIN.queryStr(sql, paras);
}
public static String queryStr(String sql) {
return MAIN.queryStr(sql);
}
public static Integer queryInt(String sql, Object... paras) {
return MAIN.queryInt(sql, paras);
}
public static Integer queryInt(String sql) {
return MAIN.queryInt(sql);
}
public static Long queryLong(String sql, Object... paras) {
return MAIN.queryLong(sql, paras);
}
public static Long queryLong(String sql) {
return MAIN.queryLong(sql);
}
public static Double queryDouble(String sql, Object... paras) {
return MAIN.queryDouble(sql, paras);
}
public static Double queryDouble(String sql) {
return MAIN.queryDouble(sql);
}
public static Float queryFloat(String sql, Object... paras) {
return MAIN.queryFloat(sql, paras);
}
public static Float queryFloat(String sql) {
return MAIN.queryFloat(sql);
}
public static BigDecimal queryBigDecimal(String sql, Object... paras) {
return MAIN.queryBigDecimal(sql, paras);
}
public static BigDecimal queryBigDecimal(String sql) {
return MAIN.queryBigDecimal(sql);
}
public static BigInteger queryBigInteger(String sql, Object... paras) {
return MAIN.queryBigInteger(sql, paras);
}
public static BigInteger queryBigInteger(String sql) {
return MAIN.queryBigInteger(sql);
}
public static byte[] queryBytes(String sql, Object... paras) {
return MAIN.queryBytes(sql, paras);
}
public static byte[] queryBytes(String sql) {
return MAIN.queryBytes(sql);
}
public static java.util.Date queryDate(String sql, Object... paras) {
return MAIN.queryDate(sql, paras);
}
public static java.util.Date queryDate(String sql) {
return MAIN.queryDate(sql);
}
public static LocalDateTime queryLocalDateTime(String sql, Object... paras) {
return MAIN.queryLocalDateTime(sql, paras);
}
public static LocalDateTime queryLocalDateTime(String sql) {
return MAIN.queryLocalDateTime(sql);
}
public static java.sql.Time queryTime(String sql, Object... paras) {
return MAIN.queryTime(sql, paras);
}
public static java.sql.Time queryTime(String sql) {
return MAIN.queryTime(sql);
}
public static java.sql.Timestamp queryTimestamp(String sql, Object... paras) {
return MAIN.queryTimestamp(sql, paras);
}
public static java.sql.Timestamp queryTimestamp(String sql) {
return MAIN.queryTimestamp(sql);
}
public static Boolean queryBoolean(String sql, Object... paras) {
return MAIN.queryBoolean(sql, paras);
}
public static Boolean queryBoolean(String sql) {
return MAIN.queryBoolean(sql);
}
public static Short queryShort(String sql, Object... paras) {
return MAIN.queryShort(sql, paras);
}
public static Short queryShort(String sql) {
return MAIN.queryShort(sql);
}
public static Byte queryByte(String sql, Object... paras) {
return MAIN.queryByte(sql, paras);
}
public static Byte queryByte(String sql) {
return MAIN.queryByte(sql);
}
public static Number queryNumber(String sql, Object... paras) {
return MAIN.queryNumber(sql, paras);
}
public static Number queryNumber(String sql) {
return MAIN.queryNumber(sql);
}
// 26 queryXxx method under -----------------------------------------------
/**
* Execute sql update
*/
static int update(Config config, Connection conn, String sql, Object... paras) throws SQLException {
return MAIN.update(config, conn, sql, paras);
}
/**
* Execute update, insert or delete sql statement.
* @param sql an SQL statement that may contain one or more '?' IN parameter placeholders
* @param paras the parameters of sql
* @return either the row count for <code>INSERT</code>, <code>UPDATE</code>,
* or <code>DELETE</code> statements, or 0 for SQL statements
* that return nothing
*/
public static int update(String sql, Object... paras) {
return MAIN.update(sql, paras);
}
/**
* @see #update(String, Object...)
* @param sql an SQL statement
*/
public static int update(String sql) {
return MAIN.update(sql);
}
static List<Record> find(Config config, Connection conn, String sql, Object... paras) throws SQLException {
return MAIN.find(config, conn, sql, paras);
}
/**
* sql paras Record
*/
public static List<Record> find(String sql, Object... paras) {
return MAIN.find(sql, paras);
}
/**
* @see #find(String, Object...)
* @param sql the sql statement
*/
public static List<Record> find(String sql) {
return MAIN.find(sql);
}
public static List<Record> findAll(String tableName) {
return MAIN.findAll(tableName);
}
/**
* Find first record. I recommend add "limit 1" in your sql.
* @param sql an SQL statement that may contain one or more '?' IN parameter placeholders
* @param paras the parameters of sql
* @return the Record object
*/
public static Record findFirst(String sql, Object... paras) {
return MAIN.findFirst(sql, paras);
}
/**
* @see #findFirst(String, Object...)
* @param sql an SQL statement
*/
public static Record findFirst(String sql) {
return MAIN.findFirst(sql);
}
/**
* Find record by id with default primary key.
* <pre>
* Example:
* Record user = Db.findById("user", 15);
* </pre>
* @param tableName the table name of the table
* @param idValue the id value of the record
*/
public static Record findById(String tableName, Object idValue) {
return MAIN.findById(tableName, idValue);
}
public static Record findById(String tableName, String primaryKey, Object idValue) {
return MAIN.findById(tableName, primaryKey, idValue);
}
/**
* Find record by ids.
* <pre>
* Example:
* Record user = Db.findByIds("user", "user_id", 123);
* Record userRole = Db.findByIds("user_role", "user_id, role_id", 123, 456);
* </pre>
* @param tableName the table name of the table
* @param primaryKey the primary key of the table, composite primary key is separated by comma character: ","
* @param idValues the id value of the record, it can be composite id values
*/
public static Record findByIds(String tableName, String primaryKey, Object... idValues) {
return MAIN.findByIds(tableName, primaryKey, idValues);
}
/**
* Delete record by id with default primary key.
* <pre>
* Example:
* Db.deleteById("user", 15);
* </pre>
* @param tableName the table name of the table
* @param idValue the id value of the record
* @return true if delete succeed otherwise false
*/
public static boolean deleteById(String tableName, Object idValue) {
return MAIN.deleteById(tableName, idValue);
}
public static boolean deleteById(String tableName, String primaryKey, Object idValue) {
return MAIN.deleteById(tableName, primaryKey, idValue);
}
/**
* Delete record by ids.
* <pre>
* Example:
* Db.deleteByIds("user", "user_id", 15);
* Db.deleteByIds("user_role", "user_id, role_id", 123, 456);
* </pre>
* @param tableName the table name of the table
* @param primaryKey the primary key of the table, composite primary key is separated by comma character: ","
* @param idValues the id value of the record, it can be composite id values
* @return true if delete succeed otherwise false
*/
public static boolean deleteByIds(String tableName, String primaryKey, Object... idValues) {
return MAIN.deleteByIds(tableName, primaryKey, idValues);
}
/**
* Delete record.
* <pre>
* Example:
* boolean succeed = Db.delete("user", "id", user);
* </pre>
* @param tableName the table name of the table
* @param primaryKey the primary key of the table, composite primary key is separated by comma character: ","
* @param record the record
* @return true if delete succeed otherwise false
*/
public static boolean delete(String tableName, String primaryKey, Record record) {
return MAIN.delete(tableName, primaryKey, record);
}
/**
* <pre>
* Example:
* boolean succeed = Db.delete("user", user);
* </pre>
* @see #delete(String, String, Record)
*/
public static boolean delete(String tableName, Record record) {
return MAIN.delete(tableName, record);
}
/**
* Execute delete sql statement.
* @param sql an SQL statement that may contain one or more '?' IN parameter placeholders
* @param paras the parameters of sql
* @return the row count for <code>DELETE</code> statements, or 0 for SQL statements
* that return nothing
*/
public static int delete(String sql, Object... paras) {
return MAIN.delete(sql, paras);
}
/**
* @see #delete(String, Object...)
* @param sql an SQL statement
*/
public static int delete(String sql) {
return MAIN.delete(sql);
}
static Page<Record> paginate(Config config, Connection conn, int pageNumber, int pageSize, String select, String sqlExceptSelect, Object... paras) throws SQLException {
return MAIN.paginate(config, conn, pageNumber, pageSize, select, sqlExceptSelect, paras);
}
/**
* Paginate.
* @param pageNumber the page number
* @param pageSize the page size
* @param select the select part of the sql statement
* @param sqlExceptSelect the sql statement excluded select part
* @param paras the parameters of sql
* @return the Page object
*/
public static Page<Record> paginate(int pageNumber, int pageSize, String select, String sqlExceptSelect, Object... paras) {
return MAIN.paginate(pageNumber, pageSize, select, sqlExceptSelect, paras);
}
public static Page<Record> paginate(int pageNumber, int pageSize, boolean isGroupBySql, String select, String sqlExceptSelect, Object... paras) {
return MAIN.paginate(pageNumber, pageSize, isGroupBySql, select, sqlExceptSelect, paras);
}
/**
*
*/
public static Page<Record> paginate(int pageNumber, int pageSize, String select, String sqlExceptSelect) {
return MAIN.paginate(pageNumber, pageSize, select, sqlExceptSelect);
}
public static Page<Record> paginateByFullSql(int pageNumber, int pageSize, String totalRowSql, String findSql, Object... paras) {
return MAIN.paginateByFullSql(pageNumber, pageSize, totalRowSql, findSql, paras);
}
public static Page<Record> paginateByFullSql(int pageNumber, int pageSize, boolean isGroupBySql, String totalRowSql, String findSql, Object... paras) {
return MAIN.paginateByFullSql(pageNumber, pageSize, isGroupBySql, totalRowSql, findSql, paras);
}
static boolean save(Config config, Connection conn, String tableName, String primaryKey, Record record) throws SQLException {
return MAIN.save(config, conn, tableName, primaryKey, record);
}
/**
* Save record.
* <pre>
* Example:
* Record userRole = new Record().set("user_id", 123).set("role_id", 456);
* Db.save("user_role", "user_id, role_id", userRole);
* </pre>
* @param tableName the table name of the table
* @param primaryKey the primary key of the table, composite primary key is separated by comma character: ","
* @param record the record will be saved
* @return true if save succeed otherwise false
*/
public static boolean save(String tableName, String primaryKey, Record record) {
return MAIN.save(tableName, primaryKey, record);
}
/**
* @see #save(String, String, Record)
*/
public static boolean save(String tableName, Record record) {
return MAIN.save(tableName, record);
}
static boolean update(Config config, Connection conn, String tableName, String primaryKey, Record record) throws SQLException {
return MAIN.update(config, conn, tableName, primaryKey, record);
}
/**
* Update Record.
* <pre>
* Example:
* Db.update("user_role", "user_id, role_id", record);
* </pre>
* @param tableName the table name of the Record save to
* @param primaryKey the primary key of the table, composite primary key is separated by comma character: ","
* @param record the Record object
* @return true if update succeed otherwise false
*/
public static boolean update(String tableName, String primaryKey, Record record) {
return MAIN.update(tableName, primaryKey, record);
}
/**
* Update record with default primary key.
* <pre>
* Example:
* Db.update("user", record);
* </pre>
* @see #update(String, String, Record)
*/
public static boolean update(String tableName, Record record) {
return MAIN.update(tableName, record);
}
/**
* @see #execute(Config, ICallback)
*/
public static Object execute(ICallback callback) {
return MAIN.execute(callback);
}
/**
* Execute callback. It is useful when all the API can not satisfy your requirement.
* @param config the Config object
* @param callback the ICallback interface
*/
static Object execute(Config config, ICallback callback) {
return MAIN.execute(config, callback);
}
/**
* Execute transaction.
* @param config the Config object
* @param transactionLevel the transaction level
* @param atom the atom operation
* @return true if transaction executing succeed otherwise false
*/
static boolean tx(Config config, int transactionLevel, IAtom atom) {
return MAIN.tx(config, transactionLevel, atom);
}
/**
* Execute transaction with default transaction level.
* @see #tx(int, IAtom)
*/
public static boolean tx(IAtom atom) {
return MAIN.tx(atom);
}
public static boolean tx(int transactionLevel, IAtom atom) {
return MAIN.tx(transactionLevel, atom);
}
/**
*
*
* https://jfinal.com/feedback/4008
*
*
*
*
* 使 txInNewThread(...)
* Connection 线 txInNewThread(...)
* 线
*/
public static Future<Boolean> txInNewThread(IAtom atom) {
return MAIN.txInNewThread(atom);
}
public static Future<Boolean> txInNewThread(int transactionLevel, IAtom atom) {
return MAIN.txInNewThread(transactionLevel, atom);
}
/**
* Find Record by cache.
* @see #find(String, Object...)
* @param cacheName the cache name
* @param key the key used to get date from cache
* @return the list of Record
*/
public static List<Record> findByCache(String cacheName, Object key, String sql, Object... paras) {
return MAIN.findByCache(cacheName, key, sql, paras);
}
/**
* @see #findByCache(String, Object, String, Object...)
*/
public static List<Record> findByCache(String cacheName, Object key, String sql) {
return MAIN.findByCache(cacheName, key, sql);
}
/**
* Find first record by cache. I recommend add "limit 1" in your sql.
* @see #findFirst(String, Object...)
* @param cacheName the cache name
* @param key the key used to get date from cache
* @param sql an SQL statement that may contain one or more '?' IN parameter placeholders
* @param paras the parameters of sql
* @return the Record object
*/
public static Record findFirstByCache(String cacheName, Object key, String sql, Object... paras) {
return MAIN.findFirstByCache(cacheName, key, sql, paras);
}
/**
* @see #findFirstByCache(String, Object, String, Object...)
*/
public static Record findFirstByCache(String cacheName, Object key, String sql) {
return MAIN.findFirstByCache(cacheName, key, sql);
}
/**
* Paginate by cache.
* @see #paginate(int, int, String, String, Object...)
* @return Page
*/
public static Page<Record> paginateByCache(String cacheName, Object key, int pageNumber, int pageSize, String select, String sqlExceptSelect, Object... paras) {
return MAIN.paginateByCache(cacheName, key, pageNumber, pageSize, select, sqlExceptSelect, paras);
}
public static Page<Record> paginateByCache(String cacheName, Object key, int pageNumber, int pageSize, boolean isGroupBySql, String select, String sqlExceptSelect, Object... paras) {
return MAIN.paginateByCache(cacheName, key, pageNumber, pageSize, isGroupBySql, select, sqlExceptSelect, paras);
}
/**
* @see #paginateByCache(String, Object, int, int, String, String, Object...)
*/
public static Page<Record> paginateByCache(String cacheName, Object key, int pageNumber, int pageSize, String select, String sqlExceptSelect) {
return MAIN.paginateByCache(cacheName, key, pageNumber, pageSize, select, sqlExceptSelect);
}
/**
* @see DbPro#batch(String, Object[][], int)
*/
public static int[] batch(String sql, Object[][] paras, int batchSize) {
return MAIN.batch(sql, paras, batchSize);
}
/**
* @see DbPro#batch(String, String, List, int)
*/
public static int[] batch(String sql, String columns, List modelOrRecordList, int batchSize) {
return MAIN.batch(sql, columns, modelOrRecordList, batchSize);
}
/**
* @see DbPro#batch(List, int)
*/
public static int[] batch(List<String> sqlList, int batchSize) {
return MAIN.batch(sqlList, batchSize);
}
/**
* @see DbPro#batchSave(List, int)
*/
public static int[] batchSave(List<? extends Model> modelList, int batchSize) {
return MAIN.batchSave(modelList, batchSize);
}
/**
* @see DbPro#batchSave(String, List, int)
*/
public static int[] batchSave(String tableName, List<? extends Record> recordList, int batchSize) {
return MAIN.batchSave(tableName, recordList, batchSize);
}
/**
* @see DbPro#batchUpdate(List, int)
*/
public static int[] batchUpdate(List<? extends Model> modelList, int batchSize) {
return MAIN.batchUpdate(modelList, batchSize);
}
/**
* @see DbPro#batchUpdate(String, String, List, int)
*/
public static int[] batchUpdate(String tableName, String primaryKey, List<? extends Record> recordList, int batchSize) {
return MAIN.batchUpdate(tableName, primaryKey, recordList, batchSize);
}
/**
* @see DbPro#batchUpdate(String, List, int)
*/
public static int[] batchUpdate(String tableName, List<? extends Record> recordList, int batchSize) {
return MAIN.batchUpdate(tableName, recordList, batchSize);
}
public static String getSql(String key) {
return MAIN.getSql(key);
}
// 支持传入变量用于 sql 生成。为了避免用户将参数拼接在 sql 中引起 sql 注入风险,只在 SqlKit 中开放该功能
// public static String getSql(String key, Map data) {
// return MAIN.getSql(key, data);
// }
public static SqlPara getSqlPara(String key, Record record) {
return MAIN.getSqlPara(key, record);
}
public static SqlPara getSqlPara(String key, Model model) {
return MAIN.getSqlPara(key, model);
}
public static SqlPara getSqlPara(String key, Map data) {
return MAIN.getSqlPara(key, data);
}
public static SqlPara getSqlPara(String key, Object... paras) {
return MAIN.getSqlPara(key, paras);
}
public static SqlPara getSqlParaByString(String content, Map data) {
return MAIN.getSqlParaByString(content, data);
}
public static SqlPara getSqlParaByString(String content, Object... paras) {
return MAIN.getSqlParaByString(content, paras);
}
public static List<Record> find(SqlPara sqlPara) {
return MAIN.find(sqlPara);
}
public static Record findFirst(SqlPara sqlPara) {
return MAIN.findFirst(sqlPara);
}
public static int update(SqlPara sqlPara) {
return MAIN.update(sqlPara);
}
public static Page<Record> paginate(int pageNumber, int pageSize, SqlPara sqlPara) {
return MAIN.paginate(pageNumber, pageSize, sqlPara);
}
public static Page<Record> paginate(int pageNumber, int pageSize, boolean isGroupBySql, SqlPara sqlPara) {
return MAIN.paginate(pageNumber, pageSize, isGroupBySql, sqlPara);
}
// ---------
/**
* Record
* <pre>
*
* Db.each(record -> {
* // 处理 record 的代码在此
*
* // 返回 true 继续循环处理下一条数据,返回 false 立即终止循环
* return true;
* }, sql, paras);
* </pre>
*/
public static void each(Function<Record, Boolean> func, String sql, Object... paras) {
MAIN.each(func, sql, paras);
}
// ---------
/**
* 使 sql Db.getSqlPara(...)
*
* <pre>
*
* Db.template("blog.find", Kv.of("id", 123).find();
* </pre>
*/
public static DbTemplate template(String key, Map data) {
return MAIN.template(key, data);
}
/**
* 使 sql Db.getSqlPara(...)
*
* <pre>
*
* Db.template("blog.find", 123).find();
* </pre>
*/
public static DbTemplate template(String key, Object... paras) {
return MAIN.template(key, paras);
}
// ---------
/**
* 使 sql sql 使
* sql
*
* <pre>
*
* String sql = "select * from blog where id = #para(id)";
* Db.templateByString(sql, Kv.of("id", 123).find();
* </pre>
*/
public static DbTemplate templateByString(String content, Map data) {
return MAIN.templateByString(content, data);
}
/**
* 使 sql sql 使
* sql
*
* <pre>
*
* String sql = "select * from blog where id = #para(0)";
* Db.templateByString(sql, 123).find();
* </pre>
*/
public static DbTemplate templateByString(String content, Object... paras) {
return MAIN.templateByString(content, paras);
}
// ---------
/**
*
*
* <pre>
*
* 1 transaction rollback
* Db.transaction( tx -> {
* tx.rollback(); // 手动回滚事务
* });
*
* 2 TransactionRollbackDecision
* public class Ret implements TransactionRollbackDecision {
* int code;
* public boolean shouldRollback() {
* return code != 200;
* }
* // ... 其它代码省略
* }
*
* Db.transaction( tx -> {
* return new Ret().code(500);
* });
*
* </pre>
*/
public static <R> R transaction(TransactionAtom<R> atom) {
return MAIN.transaction(atom);
}
/**
*
*
* transaction(TransactionAtom<R> atom)
*/
public static <R> R transaction(int transactionLevel, TransactionAtom<R> atom) {
return MAIN.transaction(transactionLevel, atom);
}
}

@ -0,0 +1,904 @@
/**
* Copyright (c) 2011-2025, James Zhan (jfinal@126.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jfinal.plugin.activerecord;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Connection;
import java.sql.SQLException;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Future;
import java.util.function.Function;
import java.util.ArrayList;
import java.util.Random;
import com.jfinal.kit.SyncWriteMap;
import com.dsideal.Config.PropKit;
/**
* Db. Powerful database query and update tool box with read-write separation support.
*
*/
@SuppressWarnings("rawtypes")
public class Db {
private static DbPro MAIN = null;
private static final Map<String, DbPro> cache = new SyncWriteMap<>(32, 0.25F);
private static final Map<String, DbPro> slaveCache = new SyncWriteMap<>(32, 0.25F);
private static final Random random = new Random();
private static List<String> slaveConfigs = new ArrayList<>();
private static boolean readWriteSeparationEnabled = false;
static {
initReadWriteSeparation();
}
/**
*
* Initialize read-write separation configuration
*/
private static void initReadWriteSeparation() {
try {
// 检查是否存在slave配置
int slaveIndex = 1;
while (true) {
try {
String slaveKey = "mysql.slave" + slaveIndex;
String slaveUrl = PropKit.get(slaveKey + ".jdbcUrl");
if (slaveUrl != null && !slaveUrl.trim().isEmpty()) {
slaveConfigs.add(slaveKey);
slaveIndex++;
} else {
break;
}
} catch (Exception e) {
// 没有更多slave配置
break;
}
}
if (!slaveConfigs.isEmpty()) {
readWriteSeparationEnabled = true;
System.out.println("[Db] 读写分离已启用,发现 " + slaveConfigs.size() + " 个从库配置");
} else {
System.out.println("[Db] 未发现从库配置,使用单库模式");
}
} catch (Exception e) {
System.out.println("[Db] 读写分离配置检查失败,使用单库模式: " + e.getMessage());
}
}
/**
* DbPro
* Get a random slave DbPro instance
*/
private static DbPro getRandomSlaveDbPro() {
if (!readWriteSeparationEnabled || slaveConfigs.isEmpty()) {
return MAIN;
}
String randomSlaveConfig = slaveConfigs.get(random.nextInt(slaveConfigs.size()));
DbPro slaveDbPro = slaveCache.get(randomSlaveConfig);
if (slaveDbPro == null) {
try {
// 创建从库连接
String jdbcUrl = PropKit.get(randomSlaveConfig + ".jdbcUrl");
String user = PropKit.get(randomSlaveConfig + ".user", PropKit.get("mysql.user"));
String password = PropKit.get(randomSlaveConfig + ".password", PropKit.get("mysql.password"));
String driverClassName = PropKit.get(randomSlaveConfig + ".driverClassName", PropKit.get("mysql.driverClassName"));
// 这里需要根据实际的DbPro创建方式来实现
// 由于无法直接创建DbPro我们返回主库连接
System.out.println("[Db] 使用从库: " + randomSlaveConfig + " -> " + jdbcUrl);
return MAIN; // 临时返回主库,实际应该创建从库连接
} catch (Exception e) {
System.err.println("[Db] 从库连接失败,回退到主库: " + e.getMessage());
return MAIN;
}
}
return slaveDbPro;
}
/**
* SQL
* Determine if SQL is a read operation
*/
private static boolean isReadOperation(String sql) {
if (sql == null) {
return false;
}
String trimmedSql = sql.trim().toLowerCase();
return trimmedSql.startsWith("select") ||
trimmedSql.startsWith("show") ||
trimmedSql.startsWith("desc") ||
trimmedSql.startsWith("describe") ||
trimmedSql.startsWith("explain");
}
/**
* SQLDbPro
* Choose appropriate DbPro instance based on SQL type
*/
private static DbPro chooseDbPro(String sql) {
if (readWriteSeparationEnabled && isReadOperation(sql)) {
return getRandomSlaveDbPro();
}
return MAIN;
}
/**
* for DbKit.addConfig(configName)
*/
static void init(String configName) {
MAIN = DbKit.getConfig(configName).dbProFactory.getDbPro(configName);
cache.put(configName, MAIN);
}
/**
* for DbKit.removeConfig(configName)
*/
static void removeDbProWithConfig(String configName) {
if (MAIN != null && MAIN.config.getName().equals(configName)) {
MAIN = null;
}
cache.remove(configName);
slaveCache.remove(configName);
}
public static DbPro use(String configName) {
DbPro result = cache.get(configName);
if (result == null) {
Config config = DbKit.getConfig(configName);
if (config == null) {
throw new IllegalArgumentException("Config not found by configName: " + configName);
}
result = config.dbProFactory.getDbPro(configName);
cache.put(configName, result);
}
return result;
}
public static DbPro use() {
return MAIN;
}
static <T> List<T> query(Config config, Connection conn, String sql, Object... paras) throws SQLException {
return MAIN.query(config, conn, sql, paras);
}
/**
* sql paras JDBC Record
*
*/
public static <T> List<T> query(String sql, Object... paras) {
return chooseDbPro(sql).query(sql, paras);
}
/**
* @see #query(String, Object...)
* @param sql an SQL statement
*/
public static <T> List<T> query(String sql) {
return chooseDbPro(sql).query(sql);
}
/**
* Execute sql query and return the first result. I recommend add "limit 1" in your sql.
*
* @param sql an SQL statement that may contain one or more '?' IN parameter placeholders
* @param paras the parameters of sql
* @return Object[] if your sql has select more than one column,
* and it return Object if your sql has select only one column.
*/
public static <T> T queryFirst(String sql, Object... paras) {
return chooseDbPro(sql).queryFirst(sql, paras);
}
/**
* @see #queryFirst(String, Object...)
* @param sql an SQL statement
*/
public static <T> T queryFirst(String sql) {
return chooseDbPro(sql).queryFirst(sql);
}
// 26 queryXxx method below -----------------------------------------------
/**
* Execute sql query just return one column.
*
* @param <T> the type of the column that in your sql's select statement
* @param sql an SQL statement that may contain one or more '?' IN parameter placeholders
* @param paras the parameters of sql
* @return <T> T
*/
public static <T> T queryColumn(String sql, Object... paras) {
return chooseDbPro(sql).queryColumn(sql, paras);
}
public static <T> T queryColumn(String sql) {
return chooseDbPro(sql).queryColumn(sql);
}
public static String queryStr(String sql, Object... paras) {
return chooseDbPro(sql).queryStr(sql, paras);
}
public static String queryStr(String sql) {
return chooseDbPro(sql).queryStr(sql);
}
public static Integer queryInt(String sql, Object... paras) {
return chooseDbPro(sql).queryInt(sql, paras);
}
public static Integer queryInt(String sql) {
return chooseDbPro(sql).queryInt(sql);
}
public static Long queryLong(String sql, Object... paras) {
return chooseDbPro(sql).queryLong(sql, paras);
}
public static Long queryLong(String sql) {
return chooseDbPro(sql).queryLong(sql);
}
public static Double queryDouble(String sql, Object... paras) {
return chooseDbPro(sql).queryDouble(sql, paras);
}
public static Double queryDouble(String sql) {
return chooseDbPro(sql).queryDouble(sql);
}
public static Float queryFloat(String sql, Object... paras) {
return chooseDbPro(sql).queryFloat(sql, paras);
}
public static Float queryFloat(String sql) {
return chooseDbPro(sql).queryFloat(sql);
}
public static BigDecimal queryBigDecimal(String sql, Object... paras) {
return chooseDbPro(sql).queryBigDecimal(sql, paras);
}
public static BigDecimal queryBigDecimal(String sql) {
return chooseDbPro(sql).queryBigDecimal(sql);
}
public static BigInteger queryBigInteger(String sql, Object... paras) {
return chooseDbPro(sql).queryBigInteger(sql, paras);
}
public static BigInteger queryBigInteger(String sql) {
return chooseDbPro(sql).queryBigInteger(sql);
}
public static byte[] queryBytes(String sql, Object... paras) {
return chooseDbPro(sql).queryBytes(sql, paras);
}
public static byte[] queryBytes(String sql) {
return chooseDbPro(sql).queryBytes(sql);
}
public static java.util.Date queryDate(String sql, Object... paras) {
return chooseDbPro(sql).queryDate(sql, paras);
}
public static java.util.Date queryDate(String sql) {
return chooseDbPro(sql).queryDate(sql);
}
public static LocalDateTime queryLocalDateTime(String sql, Object... paras) {
return chooseDbPro(sql).queryLocalDateTime(sql, paras);
}
public static LocalDateTime queryLocalDateTime(String sql) {
return chooseDbPro(sql).queryLocalDateTime(sql);
}
public static java.sql.Time queryTime(String sql, Object... paras) {
return chooseDbPro(sql).queryTime(sql, paras);
}
public static java.sql.Time queryTime(String sql) {
return chooseDbPro(sql).queryTime(sql);
}
public static java.sql.Timestamp queryTimestamp(String sql, Object... paras) {
return chooseDbPro(sql).queryTimestamp(sql, paras);
}
public static java.sql.Timestamp queryTimestamp(String sql) {
return chooseDbPro(sql).queryTimestamp(sql);
}
public static Boolean queryBoolean(String sql, Object... paras) {
return chooseDbPro(sql).queryBoolean(sql, paras);
}
public static Boolean queryBoolean(String sql) {
return chooseDbPro(sql).queryBoolean(sql);
}
public static Short queryShort(String sql, Object... paras) {
return chooseDbPro(sql).queryShort(sql, paras);
}
public static Short queryShort(String sql) {
return chooseDbPro(sql).queryShort(sql);
}
public static Byte queryByte(String sql, Object... paras) {
return chooseDbPro(sql).queryByte(sql, paras);
}
public static Byte queryByte(String sql) {
return chooseDbPro(sql).queryByte(sql);
}
public static Number queryNumber(String sql, Object... paras) {
return chooseDbPro(sql).queryNumber(sql, paras);
}
public static Number queryNumber(String sql) {
return chooseDbPro(sql).queryNumber(sql);
}
// 26 queryXxx method under -----------------------------------------------
/**
* Execute sql update
*/
static int update(Config config, Connection conn, String sql, Object... paras) throws SQLException {
return MAIN.update(config, conn, sql, paras);
}
/**
* Execute update, insert or delete sql statement.
* 使
* @param sql an SQL statement that may contain one or more '?' IN parameter placeholders
* @param paras the parameters of sql
* @return either the row count for <code>INSERT</code>, <code>UPDATE</code>,
* or <code>DELETE</code> statements, or 0 for SQL statements
* that return nothing
*/
public static int update(String sql, Object... paras) {
return MAIN.update(sql, paras);
}
/**
* @see #update(String, Object...)
* @param sql an SQL statement
*/
public static int update(String sql) {
return MAIN.update(sql);
}
static List<Record> find(Config config, Connection conn, String sql, Object... paras) throws SQLException {
return MAIN.find(config, conn, sql, paras);
}
/**
* sql paras Record
*
*/
public static List<Record> find(String sql, Object... paras) {
return chooseDbPro(sql).find(sql, paras);
}
/**
* @see #find(String, Object...)
* @param sql the sql statement
*/
public static List<Record> find(String sql) {
return chooseDbPro(sql).find(sql);
}
public static List<Record> findAll(String tableName) {
return chooseDbPro("select * from " + tableName).findAll(tableName);
}
/**
* Find first record. I recommend add "limit 1" in your sql.
*
* @param sql an SQL statement that may contain one or more '?' IN parameter placeholders
* @param paras the parameters of sql
* @return the Record object
*/
public static Record findFirst(String sql, Object... paras) {
return chooseDbPro(sql).findFirst(sql, paras);
}
/**
* @see #findFirst(String, Object...)
* @param sql an SQL statement
*/
public static Record findFirst(String sql) {
return chooseDbPro(sql).findFirst(sql);
}
/**
* Find record by id with default primary key.
*
* <pre>
* Example:
* Record user = Db.findById("user", 15);
* </pre>
* @param tableName the table name of the table
* @param idValue the id value of the record
*/
public static Record findById(String tableName, Object idValue) {
return getRandomSlaveDbPro().findById(tableName, idValue);
}
public static Record findById(String tableName, String primaryKey, Object idValue) {
return getRandomSlaveDbPro().findById(tableName, primaryKey, idValue);
}
/**
* Find record by ids.
*
* <pre>
* Example:
* Record user = Db.findByIds("user", "user_id", 123);
* Record userRole = Db.findByIds("user_role", "user_id, role_id", 123, 456);
* </pre>
* @param tableName the table name of the table
* @param primaryKey the primary key of the table, composite primary key is separated by comma character: ","
* @param idValues the id value of the record, it can be composite id values
*/
public static Record findByIds(String tableName, String primaryKey, Object... idValues) {
return getRandomSlaveDbPro().findByIds(tableName, primaryKey, idValues);
}
/**
* Delete record by id with default primary key.
* 使
* <pre>
* Example:
* Db.deleteById("user", 15);
* </pre>
* @param tableName the table name of the table
* @param idValue the id value of the record
* @return true if delete succeed otherwise false
*/
public static boolean deleteById(String tableName, Object idValue) {
return MAIN.deleteById(tableName, idValue);
}
public static boolean deleteById(String tableName, String primaryKey, Object idValue) {
return MAIN.deleteById(tableName, primaryKey, idValue);
}
/**
* Delete record by ids.
* 使
* <pre>
* Example:
* Db.deleteByIds("user", "user_id", 15);
* Db.deleteByIds("user_role", "user_id, role_id", 123, 456);
* </pre>
* @param tableName the table name of the table
* @param primaryKey the primary key of the table, composite primary key is separated by comma character: ","
* @param idValues the id value of the record, it can be composite id values
* @return true if delete succeed otherwise false
*/
public static boolean deleteByIds(String tableName, String primaryKey, Object... idValues) {
return MAIN.deleteByIds(tableName, primaryKey, idValues);
}
/**
* Delete record.
* 使
* <pre>
* Example:
* boolean succeed = Db.delete("user", "id", user);
* </pre>
* @param tableName the table name of the table
* @param primaryKey the primary key of the table, composite primary key is separated by comma character: ","
* @param record the record
* @return true if delete succeed otherwise false
*/
public static boolean delete(String tableName, String primaryKey, Record record) {
return MAIN.delete(tableName, primaryKey, record);
}
/**
* <pre>
* Example:
* boolean succeed = Db.delete("user", user);
* </pre>
* @see #delete(String, String, Record)
*/
public static boolean delete(String tableName, Record record) {
return MAIN.delete(tableName, record);
}
/**
* Execute delete sql statement.
* 使
* @param sql an SQL statement that may contain one or more '?' IN parameter placeholders
* @param paras the parameters of sql
* @return the row count for <code>DELETE</code> statements, or 0 for SQL statements
* that return nothing
*/
public static int delete(String sql, Object... paras) {
return MAIN.delete(sql, paras);
}
/**
* @see #delete(String, Object...)
* @param sql an SQL statement
*/
public static int delete(String sql) {
return MAIN.delete(sql);
}
static Page<Record> paginate(Config config, Connection conn, int pageNumber, int pageSize, String select, String sqlExceptSelect, Object... paras) throws SQLException {
return MAIN.paginate(config, conn, pageNumber, pageSize, select, sqlExceptSelect, paras);
}
/**
* Paginate.
*
* @param pageNumber the page number
* @param pageSize the page size
* @param select the select part of the sql statement
* @param sqlExceptSelect the sql statement excluded select part
* @param paras the parameters of sql
* @return the Page object
*/
public static Page<Record> paginate(int pageNumber, int pageSize, String select, String sqlExceptSelect, Object... paras) {
return getRandomSlaveDbPro().paginate(pageNumber, pageSize, select, sqlExceptSelect, paras);
}
public static Page<Record> paginate(int pageNumber, int pageSize, boolean isGroupBySql, String select, String sqlExceptSelect, Object... paras) {
return getRandomSlaveDbPro().paginate(pageNumber, pageSize, isGroupBySql, select, sqlExceptSelect, paras);
}
/**
*
*/
public static Page<Record> paginate(int pageNumber, int pageSize, String select, String sqlExceptSelect) {
return getRandomSlaveDbPro().paginate(pageNumber, pageSize, select, sqlExceptSelect);
}
public static Page<Record> paginateByFullSql(int pageNumber, int pageSize, String totalRowSql, String findSql, Object... paras) {
return getRandomSlaveDbPro().paginateByFullSql(pageNumber, pageSize, totalRowSql, findSql, paras);
}
public static Page<Record> paginateByFullSql(int pageNumber, int pageSize, boolean isGroupBySql, String totalRowSql, String findSql, Object... paras) {
return getRandomSlaveDbPro().paginateByFullSql(pageNumber, pageSize, isGroupBySql, totalRowSql, findSql, paras);
}
public static Page<Record> paginate(int pageNumber, int pageSize, SqlPara sqlPara) {
return MAIN.paginate(pageNumber, pageSize, sqlPara);
}
static boolean save(Config config, Connection conn, String tableName, String primaryKey, Record record) throws SQLException {
return MAIN.save(config, conn, tableName, primaryKey, record);
}
/**
* Save record.
* 使
* <pre>
* Example:
* Record userRole = new Record().set("user_id", 123).set("role_id", 456);
* Db.save("user_role", "user_id, role_id", userRole);
* </pre>
* @param tableName the table name of the table
* @param primaryKey the primary key of the table, composite primary key is separated by comma character: ","
* @param record the record will be saved
* @return true if save succeed otherwise false
*/
public static boolean save(String tableName, String primaryKey, Record record) {
return MAIN.save(tableName, primaryKey, record);
}
/**
* @see #save(String, String, Record)
*/
public static boolean save(String tableName, Record record) {
return MAIN.save(tableName, record);
}
static boolean update(Config config, Connection conn, String tableName, String primaryKey, Record record) throws SQLException {
return MAIN.update(config, conn, tableName, primaryKey, record);
}
/**
* Update Record.
* 使
* <pre>
* Example:
* Db.update("user_role", "user_id, role_id", record);
* </pre>
* @param tableName the table name of the Record save to
* @param primaryKey the primary key of the table, composite primary key is separated by comma character: ","
* @param record the Record object
* @return true if update succeed otherwise false
*/
public static boolean update(String tableName, String primaryKey, Record record) {
return MAIN.update(tableName, primaryKey, record);
}
/**
* Update record with default primary key.
* 使
* <pre>
* Example:
* Db.update("user", record);
* </pre>
* @see #update(String, String, Record)
*/
public static boolean update(String tableName, Record record) {
return MAIN.update(tableName, record);
}
/**
* @see #execute(Config, ICallback)
*/
public static Object execute(ICallback callback) {
return MAIN.execute(callback);
}
/**
* Execute callback. It is useful when all the API can not satisfy your requirement.
* @param config the Config object
* @param callback the ICallback interface
*/
static Object execute(Config config, ICallback callback) {
return MAIN.execute(config, callback);
}
/**
* Execute transaction.
* 使
* @param config the Config object
* @param transactionLevel the transaction level
* @param atom the atom operation
* @return true if transaction executing succeed otherwise false
*/
static boolean tx(Config config, int transactionLevel, IAtom atom) {
return MAIN.tx(config, transactionLevel, atom);
}
/**
* Execute transaction with default transaction level.
* 使
* @see #tx(int, IAtom)
*/
public static boolean tx(IAtom atom) {
return MAIN.tx(atom);
}
public static boolean tx(int transactionLevel, IAtom atom) {
return MAIN.tx(transactionLevel, atom);
}
/**
*
* 使
*
* https://jfinal.com/feedback/4008
*
*
*
*
* 使 txInNewThread(...)
* Connection 线 txInNewThread(...)
* 线
*/
public static Future<Boolean> txInNewThread(IAtom atom) {
return MAIN.txInNewThread(atom);
}
public static Future<Boolean> txInNewThread(int transactionLevel, IAtom atom) {
return MAIN.txInNewThread(transactionLevel, atom);
}
/**
* Find Record by cache.
*
* @see #find(String, Object...)
* @param cacheName the cache name
* @param key the key used to get date from cache
* @return the list of Record
*/
public static List<Record> findByCache(String cacheName, Object key, String sql, Object... paras) {
return chooseDbPro(sql).findByCache(cacheName, key, sql, paras);
}
/**
* @see #findByCache(String, Object, String, Object...)
*/
public static List<Record> findByCache(String cacheName, Object key, String sql) {
return chooseDbPro(sql).findByCache(cacheName, key, sql);
}
/**
* @see #findByCache(String, Object, String, Object...)
*/
public static Record findFirstByCache(String cacheName, Object key, String sql, Object... paras) {
return chooseDbPro(sql).findFirstByCache(cacheName, key, sql, paras);
}
/**
* @see #findFirstByCache(String, Object, String, Object...)
*/
public static Record findFirstByCache(String cacheName, Object key, String sql) {
return chooseDbPro(sql).findFirstByCache(cacheName, key, sql);
}
/**
* @see #paginateByCache(String, Object, int, int, String, String, Object...)
*/
public static Page<Record> paginateByCache(String cacheName, Object key, int pageNumber, int pageSize, String select, String sqlExceptSelect, Object... paras) {
return getRandomSlaveDbPro().paginateByCache(cacheName, key, pageNumber, pageSize, select, sqlExceptSelect, paras);
}
/**
* @see #paginateByCache(String, Object, int, int, String, String, Object...)
*/
public static Page<Record> paginateByCache(String cacheName, Object key, int pageNumber, int pageSize, boolean isGroupBySql, String select, String sqlExceptSelect, Object... paras) {
return getRandomSlaveDbPro().paginateByCache(cacheName, key, pageNumber, pageSize, isGroupBySql, select, sqlExceptSelect, paras);
}
/**
* @see #paginateByCache(String, Object, int, int, String, String, Object...)
*/
public static Page<Record> paginateByCache(String cacheName, Object key, int pageNumber, int pageSize, String select, String sqlExceptSelect) {
return getRandomSlaveDbPro().paginateByCache(cacheName, key, pageNumber, pageSize, select, sqlExceptSelect);
}
// -----------------------------
/**
*
* Get read-write separation status information
*/
public static String getReadWriteSeparationStatus() {
StringBuilder sb = new StringBuilder();
sb.append("读写分离状态: ").append(readWriteSeparationEnabled ? "已启用" : "未启用").append("\n");
if (readWriteSeparationEnabled) {
sb.append("从库配置数量: ").append(slaveConfigs.size()).append("\n");
for (int i = 0; i < slaveConfigs.size(); i++) {
sb.append("从库").append(i + 1).append(": ").append(slaveConfigs.get(i)).append("\n");
}
}
return sb.toString();
}
/**
* 使
* Force use master database for query (for scenarios requiring strong consistency)
*/
public static <T> List<T> queryFromMaster(String sql, Object... paras) {
return MAIN.query(sql, paras);
}
public static List<Record> findFromMaster(String sql, Object... paras) {
return MAIN.find(sql, paras);
}
public static Record findFirstFromMaster(String sql, Object... paras) {
return MAIN.findFirst(sql, paras);
}
/**
* 使
* Force use specific slave database for query
*/
public static <T> List<T> queryFromSlave(int slaveIndex, String sql, Object... paras) {
if (!readWriteSeparationEnabled || slaveIndex < 0 || slaveIndex >= slaveConfigs.size()) {
return MAIN.query(sql, paras);
}
// 这里应该返回指定的从库连接,暂时返回主库
return MAIN.query(sql, paras);
}
public static String getSql(String key) {
return MAIN.getSql(key);
}
public static SqlPara getSqlPara(String key) {
return MAIN.getSqlPara(key);
}
public static SqlPara getSqlPara(String key, Record record) {
return MAIN.getSqlPara(key, record);
}
public static SqlPara getSqlPara(String key, Map<?, ?> data) {
return MAIN.getSqlPara(key, data);
}
public static SqlPara getSqlPara(String key, Object... paras) {
return MAIN.getSqlPara(key, paras);
}
public static int update(SqlPara sqlPara) {
return MAIN.update(sqlPara);
}
public static List<Record> find(SqlPara sqlPara) {
return MAIN.find(sqlPara);
}
/**
* @see DbPro#batchUpdate(List, int)
*/
public static int[] batchUpdate(List<? extends Model> modelList, int batchSize) {
return MAIN.batchUpdate(modelList, batchSize);
}
/**
* @see DbPro#batchUpdate(String, String, List, int)
*/
public static int[] batchUpdate(String tableName, String primaryKey, List<? extends Record> recordList, int batchSize) {
return MAIN.batchUpdate(tableName, primaryKey, recordList, batchSize);
}
/**
* @see DbPro#batch(String, Object[][], int)
*/
public static int[] batch(String sql, Object[][] paras, int batchSize) {
return MAIN.batch(sql, paras, batchSize);
}
/**
* @see DbPro#batch(String, String, List, int)
*/
public static int[] batch(String sql, String columns, List modelOrRecordList, int batchSize) {
return MAIN.batch(sql, columns, modelOrRecordList, batchSize);
}
/**
* @see DbPro#batch(List, int)
*/
public static int[] batch(List<String> sqlList, int batchSize) {
return MAIN.batch(sqlList, batchSize);
}
/**
* @see DbPro#batchUpdate(String, List, int)
*/
public static int[] batchUpdate(String tableName, List<? extends Record> recordList, int batchSize) {
return MAIN.batchUpdate(tableName, recordList, batchSize);
}
/**
* @see DbPro#batchSave(List, int)
*/
public static int[] batchSave(List<? extends Model> modelList, int batchSize) {
return MAIN.batchSave(modelList, batchSize);
}
/**
* @see DbPro#batchSave(String, List, int)
*/
public static int[] batchSave(String tableName, List<? extends Record> recordList, int batchSize) {
return MAIN.batchSave(tableName, recordList, batchSize);
}
}

@ -4,6 +4,17 @@ mysql:
user: ylt
password: Ycharge666
jdbcUrl: jdbc:mysql://10.10.14.210:22066/base_db?reWriteBatchedInserts=true
# 从库配置示例(可选)- 如果配置了从库,读操作将自动路由到从库
# slave1:
# driverClassName: com.mysql.cj.jdbc.Driver
# user: root
# password: 123456
# jdbcUrl: jdbc:mysql://slave1:3306/dswork?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&allowMultiQueries=true
# slave2:
# driverClassName: com.mysql.cj.jdbc.Driver
# user: root
# password: 123456
# jdbcUrl: jdbc:mysql://slave2:3306/dswork?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&allowMultiQueries=true
# redis
redis:

@ -1,9 +1,20 @@
# 数据库信息
postgresql:
driverClassName: com.mysql.cj.jdbc.Driver
user: ylt
password: Ycharge666
jdbcUrl: jdbc:mysql://10.10.14.210:22066/base_db?reWriteBatchedInserts=true
driverClassName: org.postgresql.Driver
user: postgres
password: 123456
jdbcUrl: jdbc:postgresql://localhost:5432/dswork
# 从库配置示例(可选)- 如果配置了从库,读操作将自动路由到从库
# slave1:
# driverClassName: org.postgresql.Driver
# user: postgres
# password: 123456
# jdbcUrl: jdbc:postgresql://slave1:5432/dswork
# slave2:
# driverClassName: org.postgresql.Driver
# user: postgres
# password: 123456
# jdbcUrl: jdbc:postgresql://slave2:5432/dswork
# redis
redis:

@ -9,6 +9,20 @@ mysql:
user: ylt
password: Ycharge666
jdbcUrl: jdbc:mysql://10.10.14.210:22066/base_db?reWriteBatchedInserts=true
# 从库配置(读写分离)- 取消注释以启用读写分离
# 读操作将自动路由到从库,写操作仍使用主库
# slave1:
# driverClassName: com.mysql.cj.jdbc.Driver
# user: ylt
# password: Ycharge666
# jdbcUrl: jdbc:mysql://10.10.14.211:22066/base_db?reWriteBatchedInserts=true
#
# slave2:
# driverClassName: com.mysql.cj.jdbc.Driver
# user: ylt
# password: Ycharge666
# jdbcUrl: jdbc:mysql://10.10.14.212:22066/base_db?reWriteBatchedInserts=true
# redis
redis:

@ -9,6 +9,20 @@ mysql:
user: ylt
password: Ycharge666
jdbcUrl: jdbc:mysql://10.10.14.210:22066/base_db?reWriteBatchedInserts=true
# 从库配置(读写分离)- 取消注释以启用读写分离
# 读操作将自动路由到从库,写操作仍使用主库
# slave1:
# driverClassName: com.mysql.cj.jdbc.Driver
# user: ylt
# password: Ycharge666
# jdbcUrl: jdbc:mysql://10.10.14.211:22066/base_db?reWriteBatchedInserts=true
#
# slave2:
# driverClassName: com.mysql.cj.jdbc.Driver
# user: ylt
# password: Ycharge666
# jdbcUrl: jdbc:mysql://10.10.14.212:22066/base_db?reWriteBatchedInserts=true
# redis
redis:

Loading…
Cancel
Save