SpringBoot+MyBatis-Plus+Dynamic-Datasource讀寫(xiě)分離完整指南
在SpringBoot項(xiàng)目中,使用MyBatis-Plus和Dynamic-Datasource實(shí)現(xiàn)讀寫(xiě)分離是一種常見(jiàn)且高效的架構(gòu)選擇。下面我將為你詳細(xì)講解完整的實(shí)現(xiàn)方案,包括環(huán)境準(zhǔn)備、配置、代碼實(shí)現(xiàn)以及注意事項(xiàng)。
1. 讀寫(xiě)分離概述與核心概念
讀寫(xiě)分離是一種常見(jiàn)的數(shù)據(jù)庫(kù)優(yōu)化方案,其核心思想是將數(shù)據(jù)庫(kù)的寫(xiě)操作(INSERT、UPDATE、DELETE)和讀操作(SELECT)分發(fā)到不同的數(shù)據(jù)庫(kù)節(jié)點(diǎn)上。主數(shù)據(jù)庫(kù)(Master)負(fù)責(zé)處理所有寫(xiě)操作,而從數(shù)據(jù)庫(kù)(Slave)負(fù)責(zé)處理讀操作,通過(guò)數(shù)據(jù)庫(kù)的主從復(fù)制機(jī)制保持?jǐn)?shù)據(jù)同步。
1.1 核心價(jià)值
- 提升并發(fā)性能:將讀請(qǐng)求分散到多個(gè)從庫(kù),減輕主庫(kù)壓力
- 提高系統(tǒng)可用性:?jiǎn)蝹€(gè)從庫(kù)故障不影響讀服務(wù)
- 優(yōu)化響應(yīng)速度:專(zhuān)庫(kù)專(zhuān)用,避免讀寫(xiě)操作相互阻塞
1.2 技術(shù)架構(gòu)
應(yīng)用程序 → Dynamic-Datasource → 主庫(kù)(寫(xiě)操作)
↓
從庫(kù)(讀操作)
2. 環(huán)境準(zhǔn)備與依賴配置
2.1 添加Maven依賴
首先在項(xiàng)目的pom.xml中添加必要的依賴:
<dependencies>
<!-- Spring Boot Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- MyBatis-Plus 啟動(dòng)器 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.2</version>
</dependency>
<!-- Dynamic-Datasource 啟動(dòng)器(核心依賴) -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.6.1</version>
</dependency>
<!-- MySQL 驅(qū)動(dòng) -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<!-- 連接池(可選,HikariCP已內(nèi)置) -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.16</version>
</dependency>
</dependencies>
2.2 配置文件設(shè)置
在application.yml中配置主從數(shù)據(jù)源:
spring:
datasource:
dynamic:
primary: master # 設(shè)置默認(rèn)數(shù)據(jù)源為主庫(kù)
strict: false # 是否嚴(yán)格匹配數(shù)據(jù)源,false時(shí)未匹配到指定數(shù)據(jù)源則使用默認(rèn)數(shù)據(jù)源
datasource:
# 主庫(kù)配置(寫(xiě)操作)
master:
url: jdbc:mysql://master-host:3306/core?useSSL=false&serverTimezone=Asia/Shanghai
username: admin
password: master@123
driver-class-name: com.mysql.cj.jdbc.Driver
# 從庫(kù)配置(讀操作)- 支持多個(gè)從庫(kù)
slave1:
url: jdbc:mysql://slave1-host:3306/core?useSSL=false&serverTimezone=Asia/Shanghai
username: readonly
password: slave@123
driver-class-name: com.mysql.cj.jdbc.Driver
slave2:
url: jdbc:mysql://slave2-host:3306/core?useSSL=false&serverTimezone=Asia/Shanghai
username: readonly
password: slave@123
driver-class-name: com.mysql.cj.jdbc.Driver
# 負(fù)載均衡策略配置(多個(gè)從庫(kù)時(shí)生效)
strategy:
slave: round_robin # 從庫(kù)負(fù)載均衡策略:random(隨機(jī))/round_robin(輪詢)
# 連接池配置(HikariCP)
hikari:
max-pool-size: 20 # 最大連接數(shù)
min-idle: 5 # 最小空閑連接
connection-timeout: 30000 # 連接超時(shí)時(shí)間(ms)
idle-timeout: 600000 # 空閑連接超時(shí)時(shí)間(ms)
max-lifetime: 1800000 # 連接最大生命周期(ms)
# MyBatis-Plus 配置
mybatis-plus:
configuration:
map-underscore-to-camel-case: true # 開(kāi)啟駝峰命名轉(zhuǎn)換
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 打印SQL日志(調(diào)試用)
global-config:
db-config:
id-type: auto # 主鍵策略自增
3. 數(shù)據(jù)源與MyBatis-Plus配置類(lèi)
3.1 動(dòng)態(tài)數(shù)據(jù)源自動(dòng)配置
Dynamic-Datasource starter已經(jīng)提供了自動(dòng)配置,大多數(shù)情況下無(wú)需額外配置。但如果需要自定義,可以創(chuàng)建配置類(lèi):
@Configuration
@MapperScan("com.example.mapper") // 指定MyBatis mapper接口的掃描路徑
public class DataSourceConfig {
/**
* 配置動(dòng)態(tài)數(shù)據(jù)源
* Dynamic-Datasource會(huì)自動(dòng)根據(jù)application.yml的配置創(chuàng)建數(shù)據(jù)源
* 此處可以添加自定義配置
*/
@Bean
@ConfigurationProperties(prefix = "spring.datasource.dynamic")
public DynamicDataSourceProperties dynamicDataSourceProperties() {
return new DynamicDataSourceProperties();
}
/**
* 配置MyBatis-Plus攔截器(分頁(yè)插件等)
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加分頁(yè)插件
PaginationInnerInterceptor paginationInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
paginationInterceptor.setMaxLimit(1000L); // 設(shè)置最大分頁(yè)限制
interceptor.addInnerInterceptor(paginationInterceptor);
// 可以添加其他插件,如樂(lè)觀鎖插件等
return interceptor;
}
}
4. 業(yè)務(wù)層實(shí)現(xiàn)讀寫(xiě)分離
4.1 使用@DS注解手動(dòng)切換數(shù)據(jù)源
最直接的方式是在Service層使用@DS注解指定數(shù)據(jù)源:
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
/**
* 寫(xiě)操作:使用@DS("master")指定主庫(kù)
* 注意:寫(xiě)操作建議都使用主庫(kù),確保數(shù)據(jù)一致性
*/
@DS("master") // 指定使用主庫(kù)
@Transactional(rollbackFor = Exception.class) // 添加事務(wù)管理
@Override
public boolean createUser(User user) {
// 參數(shù)校驗(yàn)
if (user == null || StringUtils.isBlank(user.getUsername())) {
throw new IllegalArgumentException("用戶信息不完整");
}
// 設(shè)置創(chuàng)建時(shí)間
user.setCreateTime(LocalDateTime.now());
user.setUpdateTime(LocalDateTime.now());
// 執(zhí)行插入操作,這里會(huì)使用主庫(kù)
boolean result = save(user);
// 這里可以添加其他業(yè)務(wù)邏輯
log.info("創(chuàng)建用戶成功,用戶ID: {}", user.getId());
return result;
}
/**
* 讀操作:使用@DS("slave")指定從庫(kù)
* 框架會(huì)根據(jù)負(fù)載均衡策略選擇slave1或slave2
*/
@DS("slave") // 指定使用從庫(kù)(負(fù)載均衡)
@Override
public User getUserById(Long id) {
// 參數(shù)校驗(yàn)
if (id == null || id <= 0) {
throw new IllegalArgumentException("用戶ID不合法");
}
// 查詢用戶信息,這里會(huì)使用從庫(kù)
User user = getById(id);
if (user == null) {
log.warn("未找到對(duì)應(yīng)用戶,用戶ID: {}", id);
throw new RuntimeException("用戶不存在");
}
return user;
}
/**
* 批量查詢:同樣使用從庫(kù)
*/
@DS("slave")
@Override
public List<User> listUsersByCondition(UserQuery query) {
// 構(gòu)建查詢條件
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
if (StringUtils.isNotBlank(query.getUsername())) {
wrapper.like(User::getUsername, query.getUsername());
}
if (query.getStatus() != null) {
wrapper.eq(User::getStatus, query.getStatus());
}
// 添加排序
wrapper.orderByDesc(User::getCreateTime);
// 執(zhí)行查詢,使用從庫(kù)
return list(wrapper);
}
/**
* 更新操作:必須使用主庫(kù)
*/
@DS("master")
@Transactional(rollbackFor = Exception.class)
@Override
public boolean updateUser(User user) {
if (user == null || user.getId() == null) {
throw new IllegalArgumentException("用戶信息不完整");
}
// 設(shè)置更新時(shí)間
user.setUpdateTime(LocalDateTime.now());
// 執(zhí)行更新,使用主庫(kù)
boolean result = updateById(user);
if (result) {
log.info("更新用戶成功,用戶ID: {}", user.getId());
} else {
log.error("更新用戶失敗,用戶ID: {}", user.getId());
}
return result;
}
/**
* 刪除操作:必須使用主庫(kù)
*/
@DS("master")
@Transactional(rollbackFor = Exception.class)
@Override
public boolean deleteUser(Long id) {
if (id == null || id <= 0) {
throw new IllegalArgumentException("用戶ID不合法");
}
// 執(zhí)行刪除,使用主庫(kù)
boolean result = removeById(id);
if (result) {
log.info("刪除用戶成功,用戶ID: {}", id);
} else {
log.warn("刪除用戶失敗,用戶ID: {}", id);
}
return result;
}
}
4.2 基于AOP的自動(dòng)數(shù)據(jù)源切換(推薦)
對(duì)于大型項(xiàng)目,手動(dòng)添加@DS注解比較繁瑣,可以通過(guò)AOP自動(dòng)根據(jù)方法類(lèi)型切換數(shù)據(jù)源:
@Aspect
@Component
@Slf4j
public class DataSourceAspect {
/**
* 定義切點(diǎn):攔截Service層的所有方法
*/
@Pointcut("execution(* com.example.service..*.*(..))")
public void servicePointcut() {}
/**
* 前置通知:在方法執(zhí)行前選擇數(shù)據(jù)源
*/
@Before("servicePointcut()")
public void before(JoinPoint joinPoint) {
// 獲取方法名
String methodName = joinPoint.getSignature().getName();
// 根據(jù)方法名前綴判斷是讀操作還是寫(xiě)操作
if (isReadOperation(methodName)) {
// 讀操作使用從庫(kù)
DynamicDataSourceContextHolder.push("slave");
log.debug("切換數(shù)據(jù)源到從庫(kù),方法名: {}", methodName);
} else {
// 寫(xiě)操作使用主庫(kù)
DynamicDataSourceContextHolder.push("master");
log.debug("切換數(shù)據(jù)源到主庫(kù),方法名: {}", methodName);
}
}
/**
* 后置通知:清理數(shù)據(jù)源上下文
*/
@After("servicePointcut()")
public void after() {
DynamicDataSourceContextHolder.clear();
log.debug("清除數(shù)據(jù)源上下文");
}
/**
* 判斷是否為讀操作
* 可以根據(jù)方法名前綴進(jìn)行判斷
*/
private boolean isReadOperation(String methodName) {
// 常見(jiàn)的讀操作方法前綴
String[] readPrefixes = {"get", "select", "list", "query", "find", "search", "count", "check"};
for (String prefix : readPrefixes) {
if (methodName.startsWith(prefix)) {
return true;
}
}
return false;
}
}
使用AOP后,Service層的代碼可以簡(jiǎn)化為:
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
@Transactional(rollbackFor = Exception.class)
@Override
public boolean createUser(User user) {
// 自動(dòng)使用主庫(kù)(AOP根據(jù)方法名判斷)
user.setCreateTime(LocalDateTime.now());
return save(user);
}
@Override
public User getUserById(Long id) {
// 自動(dòng)使用從庫(kù)(AOP根據(jù)方法名前綴"get"判斷)
return getById(id);
}
// 其他方法無(wú)需添加@DS注解,由AOP自動(dòng)處理
}
5. 事務(wù)處理策略
事務(wù)處理是讀寫(xiě)分離中的關(guān)鍵問(wèn)題,需要特別注意:
5.1 單數(shù)據(jù)源事務(wù)
@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {
/**
* 單數(shù)據(jù)源事務(wù):所有操作都在主庫(kù)執(zhí)行
* 使用@DS("master")確保即使有AOP配置也使用主庫(kù)
*/
@DS("master") // 顯式指定主庫(kù)
@Transactional(rollbackFor = Exception.class) // 聲明式事務(wù)
@Override
public void createOrder(Order order) {
// 1. 保存訂單主信息(主庫(kù))
save(order);
// 2. 更新庫(kù)存(主庫(kù))
updateStock(order);
// 3. 記錄操作日志(主庫(kù))
logOrderOperation(order);
// 如果任何一步失敗,整個(gè)事務(wù)回滾
}
/**
* 只讀事務(wù):可以指定從庫(kù)
*/
@DS("slave")
@Transactional(readOnly = true) // 只讀事務(wù),有優(yōu)化效果
@Override
public Order getOrderDetail(Long orderId) {
return getById(orderId);
}
}
5.2 多數(shù)據(jù)源事務(wù)處理
對(duì)于涉及多個(gè)數(shù)據(jù)源的復(fù)雜事務(wù),需要使用分布式事務(wù)解決方案:
@Service
@Slf4j
public class DistributedTransactionService {
@Autowired
private UserService userService;
@Autowired
private OrderService orderService;
/**
* 使用@DSTransactional實(shí)現(xiàn)多數(shù)據(jù)源分布式事務(wù)
* 注意:需要集成Seata等分布式事務(wù)框架
*/
// @DSTransactional // 分布式事務(wù)注解(需要額外配置)
@Transactional(rollbackFor = Exception.class)
public void placeOrder(Order order, User user) {
try {
// 1. 用戶操作(主庫(kù))
userService.updateUser(user);
// 2. 訂單操作(主庫(kù))
orderService.createOrder(order);
// 模擬業(yè)務(wù)異常
if (order.getAmount() == null) {
throw new RuntimeException("訂單金額不能為空");
}
log.info("下單成功");
} catch (Exception e) {
log.error("下單失敗,已回滾", e);
throw e; // 拋出異常觸發(fā)回滾
}
}
}
6. 高級(jí)功能與優(yōu)化策略
6.1 多從庫(kù)負(fù)載均衡
當(dāng)配置多個(gè)從庫(kù)時(shí),Dynamic-Datasource支持多種負(fù)載均衡策略:
spring:
datasource:
dynamic:
datasource:
master:
# 主庫(kù)配置...
slave1:
# 從庫(kù)1配置...
slave2:
# 從庫(kù)2配置...
slave3:
# 從庫(kù)3配置...
strategy:
slave: round_robin # 負(fù)載均衡策略
# 可選值:
# random - 隨機(jī)選擇(默認(rèn))
# round_robin - 輪詢
# weight_round_robin - 加權(quán)輪詢(需要額外配置權(quán)重)
負(fù)載均衡策略對(duì)比:
| 策略類(lèi)型 | 描述 | 適用場(chǎng)景 |
|---|---|---|
| random | 隨機(jī)選擇從庫(kù) | 簡(jiǎn)單的讀負(fù)載均衡 |
| round_robin | 輪詢選擇從庫(kù) | 從庫(kù)配置相近,需要均勻分布 |
| weight_round_robin | 根據(jù)權(quán)重選擇 | 從庫(kù)配置不同,按性能分配負(fù)載 |
6.2 健康檢查與故障轉(zhuǎn)移
@Component
@Slf4j
public class DataSourceHealthCheck {
@Autowired
private DataSource dataSource;
/**
* 定時(shí)檢查從庫(kù)健康狀態(tài)
*/
@Scheduled(fixedRate = 60000) // 每分鐘執(zhí)行一次
public void checkSlaveHealth() {
// 這里可以實(shí)現(xiàn)從庫(kù)健康檢查邏輯
// 如果某個(gè)從庫(kù)不可用,可以動(dòng)態(tài)將其從負(fù)載均衡池中移除
log.info("執(zhí)行從庫(kù)健康檢查...");
// 實(shí)際實(shí)現(xiàn)中可以使用JDBC測(cè)試連接或查詢數(shù)據(jù)庫(kù)狀態(tài)
}
/**
* 獲取從庫(kù)同步延遲
*/
public Long getReplicationDelay(String slaveName) {
// 執(zhí)行SQL查詢從庫(kù)同步狀態(tài)
// SHOW SLAVE STATUS 可以獲取Seconds_Behind_Master等信息
// 如果延遲過(guò)大,可以臨時(shí)將該從庫(kù)標(biāo)記為不可用
return 0L; // 返回延遲秒數(shù)
}
}
7. 測(cè)試與驗(yàn)證
7.1 單元測(cè)試
@SpringBootTest
@Slf4j
class ReadWriteSeparationTest {
@Autowired
private UserService userService;
/**
* 測(cè)試寫(xiě)操作(應(yīng)該路由到主庫(kù))
*/
@Test
void testWriteOperation() {
User user = new User();
user.setUsername("testUser");
user.setEmail("test@example.com");
boolean result = userService.createUser(user);
assertTrue(result);
log.info("寫(xiě)操作測(cè)試通過(guò),應(yīng)路由到主庫(kù)");
}
/**
* 測(cè)試讀操作(應(yīng)該路由到從庫(kù))
*/
@Test
void testReadOperation() {
User user = userService.getUserById(1L);
assertNotNull(user);
log.info("讀操作測(cè)試通過(guò),應(yīng)路由到從庫(kù)");
}
/**
* 測(cè)試事務(wù)內(nèi)的數(shù)據(jù)源選擇
*/
@Test
void testTransactionalOperation() {
// 測(cè)試事務(wù)方法,應(yīng)始終使用主庫(kù)
User user = userService.getUserById(1L);
assertNotNull(user);
log.info("事務(wù)內(nèi)操作測(cè)試完成");
}
}
7.2 驗(yàn)證數(shù)據(jù)源路由
在application.yml中開(kāi)啟SQL日志,驗(yàn)證讀寫(xiě)分離是否生效:
# 開(kāi)啟MyBatis SQL日志
logging:
level:
com.example.mapper: debug # Mapper接口的包路徑
com.baomidou.dynamic.datasource: debug # Dynamic-Datasource日志
觀察控制臺(tái)輸出,應(yīng)該能看到類(lèi)似日志:
2025-11-11 10:00:00 | Master DataSource | INSERT INTO user ... 2025-11-11 10:00:05 | Slave DataSource | SELECT * FROM user ...
8. 生產(chǎn)環(huán)境注意事項(xiàng)
8.1 主從同步延遲處理
@Service
public class CriticalReadService {
@Autowired
private UserMapper userMapper;
/**
* 對(duì)于一致性要求高的讀操作,強(qiáng)制走主庫(kù)
*/
@DS("master") // 強(qiáng)制使用主庫(kù),避免讀取舊數(shù)據(jù)
public User getCriticalUserInfo(Long userId) {
// 例如:賬戶余額、訂單狀態(tài)等關(guān)鍵信息
return userMapper.selectById(userId);
}
/**
* 檢查數(shù)據(jù)是否已同步
*/
public boolean waitForReplication(Long userId, int maxWaitSeconds) {
for (int i = 0; i < maxWaitSeconds; i++) {
User masterUser = getFromMaster(userId);
User slaveUser = getFromSlave(userId);
if (Objects.equals(masterUser, slaveUser)) {
return true; // 數(shù)據(jù)已同步
}
try {
Thread.sleep(1000); // 等待1秒
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
return false; // 同步超時(shí)
}
}
8.2 監(jiān)控與告警
# 連接池監(jiān)控配置
spring:
datasource:
dynamic:
druid:
# 開(kāi)啟監(jiān)控統(tǒng)計(jì)
filters: stat,wall,log4j
web-stat-filter:
enabled: true
stat-view-servlet:
enabled: true
url-pattern: /druid/*
reset-enable: false
login-username: admin
login-password: admin
9. 常見(jiàn)問(wèn)題與解決方案
9.1 事務(wù)內(nèi)數(shù)據(jù)源切換失效
問(wèn)題:在@Transactional方法內(nèi),數(shù)據(jù)源在方法開(kāi)始時(shí)確定,方法內(nèi)部調(diào)用無(wú)法切換數(shù)據(jù)源。
解決方案:
@Service
public class TransactionalService {
@Autowired
private ApplicationContext applicationContext;
@Transactional
public void transactionalMethod() {
// 方法內(nèi)需要切換數(shù)據(jù)源時(shí),通過(guò)代理對(duì)象調(diào)用
TransactionalService self = applicationContext.getBean(TransactionalService.class);
// 通過(guò)代理對(duì)象調(diào)用,可以正常切換數(shù)據(jù)源
self.readOperation(); // 使用從庫(kù)
self.writeOperation(); // 使用主庫(kù)
}
@DS("slave")
public void readOperation() {
// 讀操作
}
@DS("master")
public void writeOperation() {
// 寫(xiě)操作
}
}
9.2 動(dòng)態(tài)增減數(shù)據(jù)源
@Service
public class DynamicDataSourceService {
@Autowired
private DynamicDataSourceProvider dataSourceProvider;
/**
* 運(yùn)行時(shí)動(dòng)態(tài)添加數(shù)據(jù)源
*/
public void addDataSource(String dataSourceName, DataSourceProperty property) {
DynamicDataSourceContextHolder.addDataSource(dataSourceName, property);
log.info("動(dòng)態(tài)添加數(shù)據(jù)源: {}", dataSourceName);
}
/**
* 運(yùn)行時(shí)移除數(shù)據(jù)源
*/
public void removeDataSource(String dataSourceName) {
DynamicDataSourceContextHolder.removeDataSource(dataSourceName);
log.info("動(dòng)態(tài)移除數(shù)據(jù)源: {}", dataSourceName);
}
}
總結(jié)
通過(guò)SpringBoot + MyBatis-Plus + Dynamic-Datasource實(shí)現(xiàn)讀寫(xiě)分離,可以顯著提升數(shù)據(jù)庫(kù)讀性能和高可用性。關(guān)鍵點(diǎn)包括:
- 正確配置:確保主從數(shù)據(jù)源配置正確,負(fù)載均衡策略合理
- 合理使用注解:在適當(dāng)?shù)姆椒ㄉ鲜褂?code>@DS注解或通過(guò)AOP自動(dòng)切換
- 事務(wù)管理:注意事務(wù)內(nèi)的數(shù)據(jù)源行為,關(guān)鍵操作強(qiáng)制走主庫(kù)
- 監(jiān)控告警:建立完善的監(jiān)控體系,及時(shí)發(fā)現(xiàn)主從延遲等問(wèn)題
- 故障處理:實(shí)現(xiàn)從庫(kù)故障自動(dòng)轉(zhuǎn)移和恢復(fù)機(jī)制
這種架構(gòu)在讀寫(xiě)比例高的應(yīng)用中能帶來(lái)顯著的性能提升,但也需要處理好主從同步延遲等一致性問(wèn)題。
到此這篇關(guān)于SpringBoot+MyBatis-Plus+Dynamic-Datasource讀寫(xiě)分離完整指南的文章就介紹到這了,更多相關(guān)SpringBoot MyBatis-Plus 讀寫(xiě)分離內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
jdk8?FunctionalInterface注解源碼解讀
這篇文章主要介紹了jdk8?FunctionalInterface注解源碼解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11
Java實(shí)現(xiàn)批量修改文件名和重命名的方法
這篇文章主要介紹了Java實(shí)現(xiàn)批量修改文件名和重命名的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09
Java的Hibernate框架中的組合映射學(xué)習(xí)教程
組合映射即是指主對(duì)象和子對(duì)象關(guān)聯(lián)且擁有相同的生命周期的映射關(guān)系,這里我們將舉一些數(shù)據(jù)操作的實(shí)例,來(lái)講解Java的Hibernate框架中的組合映射學(xué)習(xí)教程2016-07-07
Spring學(xué)習(xí)筆記之RedisTemplate的配置與使用教程
這篇文章主要給大家介紹了關(guān)于Spring學(xué)習(xí)筆記之RedisTemplate配置與使用的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用spring具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-06-06
舉例講解Java中synchronized關(guān)鍵字的用法
這篇文章主要介紹了Java中synchronized關(guān)鍵字的用法,針對(duì)synchronized修飾方法的使用作出了簡(jiǎn)單講解和演示,需要的朋友可以參考下2016-04-04
Mybatis動(dòng)態(tài)sql超詳細(xì)講解
動(dòng)態(tài)SQL是MyBatis的強(qiáng)大特性之一,顧名思義就是會(huì)動(dòng)的SQL,即是能夠靈活的根據(jù)某種條件拼接出完整的SQL語(yǔ)句,下面這篇文章主要給大家介紹了關(guān)于Mybatis動(dòng)態(tài)sql的相關(guān)資料,需要的朋友可以參考下2023-04-04

