欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

SpringBoot+ThreadLocal+AbstractRoutingDataSource實(shí)現(xiàn)動(dòng)態(tài)切換數(shù)據(jù)源

 更新時(shí)間:2023年08月08日 11:11:31   作者:搶老婆酸奶的小肥仔  
最近在做業(yè)務(wù)需求時(shí),需要從不同的數(shù)據(jù)庫(kù)中獲取數(shù)據(jù)然后寫入到當(dāng)前數(shù)據(jù)庫(kù)中,因此涉及到切換數(shù)據(jù)源問(wèn)題,所以本文采用ThreadLocal+AbstractRoutingDataSource來(lái)模擬實(shí)現(xiàn)dynamic-datasource-spring-boot-starter中線程數(shù)據(jù)源切換,需要的朋友可以參考下

最近在做業(yè)務(wù)需求時(shí),需要從不同的數(shù)據(jù)庫(kù)中獲取數(shù)據(jù)然后寫入到當(dāng)前數(shù)據(jù)庫(kù)中,因此涉及到切換數(shù)據(jù)源問(wèn)題。本來(lái)想著使用Mybatis-plus中提供的動(dòng)態(tài)數(shù)據(jù)源SpringBoot的starter:dynamic-datasource-spring-boot-starter來(lái)實(shí)現(xiàn)。結(jié)果引入后發(fā)現(xiàn)由于之前項(xiàng)目環(huán)境問(wèn)題導(dǎo)致無(wú)法使用。然后研究了下數(shù)據(jù)源切換代碼,決定自己采用ThreadLocal+AbstractRoutingDataSource來(lái)模擬實(shí)現(xiàn)dynamic-datasource-spring-boot-starter中線程數(shù)據(jù)源切換。

1、簡(jiǎn)介

上述提到了ThreadLocal和AbstractRoutingDataSource,我們來(lái)對(duì)其進(jìn)行簡(jiǎn)單介紹下。

ThreadLocal :想必大家必不會(huì)陌生,全稱:thread local variable。主要是為解決多線程時(shí)由于并發(fā)而產(chǎn)生數(shù)據(jù)不一致問(wèn)題。ThreadLocal為每個(gè)線程提供變量副本,確保每個(gè)線程在某一時(shí)間訪問(wèn)到的不是同一個(gè)對(duì)象,這樣做到了隔離性,增加了內(nèi)存,但大大減少了線程同步時(shí)的性能消耗,減少了線程并發(fā)控制的復(fù)雜程度。

ThreadLocal作用:在一個(gè)線程中共享,不同線程間隔離

ThreadLocal原理:ThreadLocal存入值時(shí),會(huì)獲取當(dāng)前線程實(shí)例作為key,存入當(dāng)前線程對(duì)象中的Map中。

AbstractRoutingDataSource :根據(jù)用戶定義的規(guī)則選擇當(dāng)前的數(shù)據(jù)源,

作用:在執(zhí)行查詢之前,設(shè)置使用的數(shù)據(jù)源,實(shí)現(xiàn)動(dòng)態(tài)路由的數(shù)據(jù)源,在每次數(shù)據(jù)庫(kù)查詢操作前執(zhí)行它的抽象方法determineCurrentLookupKey(),決定使用哪個(gè)數(shù)據(jù)源。

2、代碼實(shí)現(xiàn)

程序環(huán)境:

SpringBoot2.4.8

Mybatis-plus3.2.0

Druid1.2.6

lombok1.18.20

commons-lang3 3.10

2.1 實(shí)現(xiàn)ThreadLocal

創(chuàng)建一個(gè)類用于實(shí)現(xiàn)ThreadLocal,主要是通過(guò)get,set,remove方法來(lái)獲取、設(shè)置、刪除當(dāng)前線程對(duì)應(yīng)的數(shù)據(jù)源。

/**
 * @author: jiangjs
 * @description:
 * @date: 2023/7/27 11:21
 **/
public class DataSourceContextHolder {
    //此類提供線程局部變量。這些變量不同于它們的正常對(duì)應(yīng)關(guān)系是每個(gè)線程訪問(wèn)一個(gè)線程(通過(guò)get、set方法),有自己的獨(dú)立初始化變量的副本。
    private static final ThreadLocal<String> DATASOURCE_HOLDER = new ThreadLocal<>();
    /**
     * 設(shè)置數(shù)據(jù)源
     * @param dataSourceName 數(shù)據(jù)源名稱
     */
    public static void setDataSource(String dataSourceName){
        DATASOURCE_HOLDER.set(dataSourceName);
    }
    /**
     * 獲取當(dāng)前線程的數(shù)據(jù)源
     * @return 數(shù)據(jù)源名稱
     */
    public static String getDataSource(){
        return DATASOURCE_HOLDER.get();
    }
    /**
     * 刪除當(dāng)前數(shù)據(jù)源
     */
    public static void removeDataSource(){
        DATASOURCE_HOLDER.remove();
    }
}

2.2 實(shí)現(xiàn)AbstractRoutingDataSource

定義一個(gè)動(dòng)態(tài)數(shù)據(jù)源類實(shí)現(xiàn)AbstractRoutingDataSource,通過(guò)determineCurrentLookupKey方法與上述實(shí)現(xiàn)的ThreadLocal類中的get方法進(jìn)行關(guān)聯(lián),實(shí)現(xiàn)動(dòng)態(tài)切換數(shù)據(jù)源。

/**
 * @author: jiangjs
 * @description: 實(shí)現(xiàn)動(dòng)態(tài)數(shù)據(jù)源,根據(jù)AbstractRoutingDataSource路由到不同數(shù)據(jù)源中
 * @date: 2023/7/27 11:18
 **/
public class DynamicDataSource extends AbstractRoutingDataSource {
    public DynamicDataSource(DataSource defaultDataSource,Map<Object, Object> targetDataSources){
        super.setDefaultTargetDataSource(defaultDataSource);
        super.setTargetDataSources(targetDataSources);
    }
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDataSource();
    }
}

上述代碼中,還實(shí)現(xiàn)了一個(gè)動(dòng)態(tài)數(shù)據(jù)源類的構(gòu)造方法,主要是為了設(shè)置默認(rèn)數(shù)據(jù)源,以及以Map保存的各種目標(biāo)數(shù)據(jù)源。其中Map的key是設(shè)置的數(shù)據(jù)源名稱,value則是對(duì)應(yīng)的數(shù)據(jù)源(DataSource)。

2.3 配置數(shù)據(jù)庫(kù)

application.yml中配置數(shù)據(jù)庫(kù)信息:

#設(shè)置數(shù)據(jù)源
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      master:
        url: jdbc:mysql://xxxxxx:3306/test1?characterEncoding=utf-8&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull&useSSL=false
        username: root
        password: 123456
        driver-class-name: com.mysql.cj.jdbc.Driver
      slave:
        url: jdbc:mysql://xxxxx:3306/test2?characterEncoding=utf-8&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull&useSSL=false
        username: root
        password: 123456
        driver-class-name: com.mysql.cj.jdbc.Driver
      initial-size: 15
      min-idle: 15
      max-active: 200
      max-wait: 60000
      time-between-eviction-runs-millis: 60000
      min-evictable-idle-time-millis: 300000
      validation-query: ""
      test-while-idle: true
      test-on-borrow: false
      test-on-return: false
      pool-prepared-statements: false
      connection-properties: false
/**
 * @author: jiangjs
 * @description: 設(shè)置數(shù)據(jù)源
 * @date: 2023/7/27 11:34
 **/
@Configuration
public class DateSourceConfig {
    @Bean
    @ConfigurationProperties("spring.datasource.druid.master")
    public DataSource masterDataSource(){
        return DruidDataSourceBuilder.create().build();
    }
    @Bean
    @ConfigurationProperties("spring.datasource.druid.slave")
    public DataSource slaveDataSource(){
        return DruidDataSourceBuilder.create().build();
    }
    @Bean(name = "dynamicDataSource")
    @Primary
    public DynamicDataSource createDynamicDataSource(){
        Map<Object,Object> dataSourceMap = new HashMap<>();
        DataSource defaultDataSource = masterDataSource();
        dataSourceMap.put("master",defaultDataSource);
        dataSourceMap.put("slave",slaveDataSource());
        return new DynamicDataSource(defaultDataSource,dataSourceMap);
    }
}

通過(guò)配置類,將配置文件中的配置的數(shù)據(jù)庫(kù)信息轉(zhuǎn)換成datasource,并添加到DynamicDataSource中,同時(shí)通過(guò)@Bean將DynamicDataSource注入Spring中進(jìn)行管理,后期在進(jìn)行動(dòng)態(tài)數(shù)據(jù)源添加時(shí),會(huì)用到。

2.4 測(cè)試

在主從兩個(gè)測(cè)試庫(kù)中,分別添加一張表test_user,里面只有一個(gè)字段user_name。

create table test_user(
  user_name varchar(255) not null comment '用戶名'
)

在主庫(kù)添加信息:

insert into test_user (user_name) value ('master');

從庫(kù)中添加信息:

insert into test_user (user_name) value ('slave'); 

我們創(chuàng)建一個(gè)getData的方法,參數(shù)就是需要查詢數(shù)據(jù)的數(shù)據(jù)源名稱。

@GetMapping("/getData.do/{datasourceName}")
public String getMasterData(@PathVariable("datasourceName") String datasourceName){
    DataSourceContextHolder.setDataSource(datasourceName);
    TestUser testUser = testUserMapper.selectOne(null);
    DataSourceContextHolder.removeDataSource();
    return testUser.getUserName();
}

其他的Mapper和實(shí)體類大家自行實(shí)現(xiàn)。

執(zhí)行結(jié)果:

1、傳遞master時(shí):

2、傳遞slave時(shí):

通過(guò)執(zhí)行結(jié)果,我們看到傳遞不同的數(shù)據(jù)源名稱,查詢對(duì)應(yīng)的數(shù)據(jù)庫(kù)是不一樣的,返回結(jié)果也不一樣。

在上述代碼中,我們看到 DataSourceContextHolder.setDataSource(datasourceName); 來(lái)設(shè)置了當(dāng)前線程需要查詢的數(shù)據(jù)庫(kù),通過(guò) DataSourceContextHolder.removeDataSource(); 來(lái)移除當(dāng)前線程已設(shè)置的數(shù)據(jù)源。使用過(guò)Mybatis-plus動(dòng)態(tài)數(shù)據(jù)源的小伙伴,應(yīng)該還記得我們?cè)谑褂们袚Q數(shù)據(jù)源時(shí)會(huì)使用到 DynamicDataSourceContextHolder.push(String ds); DynamicDataSourceContextHolder.poll(); 這兩個(gè)方法,翻看源碼我們會(huì)發(fā)現(xiàn)其實(shí)就是在使用ThreadLocal時(shí)使用了棧,這樣的好處就是能使用多數(shù)據(jù)源嵌套,這里就不帶大家實(shí)現(xiàn)了,有興趣的小伙伴可以看看Mybatis-plus中動(dòng)態(tài)數(shù)據(jù)源的源碼。

注:?jiǎn)?dòng)程序時(shí),小伙伴不要忘記將SpringBoot自動(dòng)添加數(shù)據(jù)源進(jìn)行排除哦,否則會(huì)報(bào)循環(huán)依賴問(wèn)題。

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)

2.5 優(yōu)化調(diào)整

2.5.1 注解切換數(shù)據(jù)源

在上述中,雖然已經(jīng)實(shí)現(xiàn)了動(dòng)態(tài)切換數(shù)據(jù)源,但是我們會(huì)發(fā)現(xiàn)如果涉及到多個(gè)業(yè)務(wù)進(jìn)行切換數(shù)據(jù)源的話,我們就需要在每一個(gè)實(shí)現(xiàn)類中添加這一段代碼。

說(shuō)到這有小伙伴應(yīng)該就會(huì)想到使用注解來(lái)進(jìn)行優(yōu)化,接下來(lái)我們來(lái)實(shí)現(xiàn)一下。

2.5.1.1 定義注解

我們就用mybatis動(dòng)態(tài)數(shù)據(jù)源切換的注解:DS,代碼如下:

/**
 * @author: jiangjs
 * @description:
 * @date: 2023/7/27 14:39
 **/
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface DS {
    String value() default "master";
}

2.5.1.2 實(shí)現(xiàn)aop

@Aspect
@Component
@Slf4j
public class DSAspect {
    @Pointcut("@annotation(com.jiashn.dynamic_datasource.dynamic.aop.DS)")
    public void dynamicDataSource(){}
    @Around("dynamicDataSource()")
    public Object datasourceAround(ProceedingJoinPoint point) throws Throwable {
        MethodSignature signature = (MethodSignature)point.getSignature();
        Method method = signature.getMethod();
        DS ds = method.getAnnotation(DS.class);
        if (Objects.nonNull(ds)){
            DataSourceContextHolder.setDataSource(ds.value());
        }
        try {
            return point.proceed();
        } finally {
            DataSourceContextHolder.removeDataSource();
        }
    }
}

代碼使用了@Around,通過(guò)ProceedingJoinPoint獲取注解信息,拿到注解傳遞值,然后設(shè)置當(dāng)前線程的數(shù)據(jù)源。對(duì)aop不了解的小伙伴可以自行g(shù)oogle或百度。

2.5.1.3 測(cè)試

添加兩個(gè)測(cè)試方法:

@GetMapping("/getMasterData.do")
public String getMasterData(){
    TestUser testUser = testUserMapper.selectOne(null);
    return testUser.getUserName();
}
@GetMapping("/getSlaveData.do")
@DS("slave")
public String getSlaveData(){
    TestUser testUser = testUserMapper.selectOne(null);
    return testUser.getUserName();
}

由于@DS中設(shè)置的默認(rèn)值是:master,因此在調(diào)用主數(shù)據(jù)源時(shí),可以不用進(jìn)行添加。

執(zhí)行結(jié)果:

1、調(diào)用getMasterData.do方法:

2、調(diào)用getSlaveData.do方法:

通過(guò)執(zhí)行結(jié)果,我們通過(guò)@DS也進(jìn)行了數(shù)據(jù)源的切換,實(shí)現(xiàn)了Mybatis-plus動(dòng)態(tài)切換數(shù)據(jù)源中的通過(guò)注解切換數(shù)據(jù)源的方式。

2.5.2 動(dòng)態(tài)添加數(shù)據(jù)源

業(yè)務(wù)場(chǎng)景:有時(shí)候我們的業(yè)務(wù)會(huì)要求我們從保存有其他數(shù)據(jù)源的數(shù)據(jù)庫(kù)表中添加這些數(shù)據(jù)源,然后再根據(jù)不同的情況切換這些數(shù)據(jù)源。

因此我們需要改造下DynamicDataSource來(lái)實(shí)現(xiàn)動(dòng)態(tài)加載數(shù)據(jù)源。

2.5.2.1 數(shù)據(jù)源實(shí)體

/**
 * @author: jiangjs
 * @description: 數(shù)據(jù)源實(shí)體
 * @date: 2023/7/27 15:55
 **/
@Data
@Accessors(chain = true)
public class DataSourceEntity {
    /**
     * 數(shù)據(jù)庫(kù)地址
     */
    private String url;
    /**
     * 數(shù)據(jù)庫(kù)用戶名
     */
    private String userName;
    /**
     * 密碼
     */
    private String passWord;
    /**
     * 數(shù)據(jù)庫(kù)驅(qū)動(dòng)
     */
    private String driverClassName;
    /**
     * 數(shù)據(jù)庫(kù)key,即保存Map中的key
     */
    private String key;
}

實(shí)體中定義數(shù)據(jù)源的一般信息,同時(shí)定義一個(gè)key用于作為DynamicDataSource中Map中的key。

2.5.2.2 修改DynamicDataSource

/**
 * @author: jiangjs
 * @description: 實(shí)現(xiàn)動(dòng)態(tài)數(shù)據(jù)源,根據(jù)AbstractRoutingDataSource路由到不同數(shù)據(jù)源中
 * @date: 2023/7/27 11:18
 **/
@Slf4j
public class DynamicDataSource extends AbstractRoutingDataSource {
    private final Map<Object,Object> targetDataSourceMap;
    public DynamicDataSource(DataSource defaultDataSource,Map<Object, Object> targetDataSources){
        super.setDefaultTargetDataSource(defaultDataSource);
        super.setTargetDataSources(targetDataSources);
        this.targetDataSourceMap = targetDataSources;
    }
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDataSource();
    }
    /**
     * 添加數(shù)據(jù)源信息
     * @param dataSources 數(shù)據(jù)源實(shí)體集合
     * @return 返回添加結(jié)果
     */
    public void createDataSource(List<DataSourceEntity> dataSources){
        try {
            if (CollectionUtils.isNotEmpty(dataSources)){
                for (DataSourceEntity ds : dataSources) {
                    //校驗(yàn)數(shù)據(jù)庫(kù)是否可以連接
                    Class.forName(ds.getDriverClassName());
                    DriverManager.getConnection(ds.getUrl(),ds.getUserName(),ds.getPassWord());
                    //定義數(shù)據(jù)源
                    DruidDataSource dataSource = new DruidDataSource();
                    BeanUtils.copyProperties(ds,dataSource);
                    //申請(qǐng)連接時(shí)執(zhí)行validationQuery檢測(cè)連接是否有效,這里建議配置為TRUE,防止取到的連接不可用
                    dataSource.setTestOnBorrow(true);
                    //建議配置為true,不影響性能,并且保證安全性。
                    //申請(qǐng)連接的時(shí)候檢測(cè),如果空閑時(shí)間大于timeBetweenEvictionRunsMillis,執(zhí)行validationQuery檢測(cè)連接是否有效。
                    dataSource.setTestWhileIdle(true);
                    //用來(lái)檢測(cè)連接是否有效的sql,要求是一個(gè)查詢語(yǔ)句。
                    dataSource.setValidationQuery("select 1 ");
                    dataSource.init();
                    this.targetDataSourceMap.put(ds.getKey(),dataSource);
                }
                super.setTargetDataSources(this.targetDataSourceMap);
                // 將TargetDataSources中的連接信息放入resolvedDataSources管理
                super.afterPropertiesSet();
                return Boolean.TRUE;
            }
        }catch (ClassNotFoundException | SQLException e) {
            log.error("---程序報(bào)錯(cuò)---:{}", e.getMessage());
        }
        return Boolean.FALSE;
    }
    /**
     * 校驗(yàn)數(shù)據(jù)源是否存在
     * @param key 數(shù)據(jù)源保存的key
     * @return 返回結(jié)果,true:存在,false:不存在
     */
    public boolean existsDataSource(String key){
        return Objects.nonNull(this.targetDataSourceMap.get(key));
    }
}

在改造后的DynamicDataSource中,我們添加可以一個(gè) private final Map<Object,Object> targetDataSourceMap,這個(gè)map會(huì)在添加數(shù)據(jù)源的配置文件時(shí)將創(chuàng)建的Map數(shù)據(jù)源信息通過(guò)DynamicDataSource構(gòu)造方法進(jìn)行初始賦值,即:DateSourceConfig類中的createDynamicDataSource()方法中。

同時(shí)我們?cè)谠擃愔刑砑恿艘粋€(gè)createDataSource方法,進(jìn)行數(shù)據(jù)源的創(chuàng)建,并添加到map中,再通過(guò) super.setTargetDataSources(this.targetDataSourceMap) ;進(jìn)行目標(biāo)數(shù)據(jù)源的重新賦值。

2.5.2.3 動(dòng)態(tài)添加數(shù)據(jù)源

上述代碼已經(jīng)實(shí)現(xiàn)了添加數(shù)據(jù)源的方法,那么我們來(lái)模擬通過(guò)從數(shù)據(jù)庫(kù)表中添加數(shù)據(jù)源,然后我們通過(guò)調(diào)用加載數(shù)據(jù)源的方法將數(shù)據(jù)源添加進(jìn)數(shù)據(jù)源Map中。

在主數(shù)據(jù)庫(kù)中定義一個(gè)數(shù)據(jù)庫(kù)表,用于保存數(shù)據(jù)庫(kù)信息。

create table test_db_info(
    id int auto_increment primary key not null comment '主鍵Id',
    url varchar(255) not null comment '數(shù)據(jù)庫(kù)URL',
    username varchar(255) not null comment '用戶名',
    password varchar(255) not null comment '密碼',
    driver_class_name varchar(255) not null comment '數(shù)據(jù)庫(kù)驅(qū)動(dòng)'
    name varchar(255) not null comment '數(shù)據(jù)庫(kù)名稱'
)

為了方便,我們將之前的從庫(kù)錄入到數(shù)據(jù)庫(kù)中,修改數(shù)據(jù)庫(kù)名稱。

insert into test_db_info(url, username, password,driver_class_name, name)
value ('jdbc:mysql://xxxxx:3306/test2?characterEncoding=utf-8&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull&useSSL=false',
       'root','123456','com.mysql.cj.jdbc.Driver','add_slave')

數(shù)據(jù)庫(kù)表對(duì)應(yīng)的實(shí)體、mapper,小伙伴們自行添加。

啟動(dòng)SpringBoot時(shí)添加數(shù)據(jù)源:

/**
 * @author: jiangjs
 * @description:
 * @date: 2023/7/27 16:56
 **/
@Component
public class LoadDataSourceRunner implements CommandLineRunner {
    @Resource
    private DynamicDataSource dynamicDataSource;
    @Resource
    private TestDbInfoMapper testDbInfoMapper;
    @Override
    public void run(String... args) throws Exception {
        List<TestDbInfo> testDbInfos = testDbInfoMapper.selectList(null);
        if (CollectionUtils.isNotEmpty(testDbInfos)) {
            List<DataSourceEntity> ds = new ArrayList<>();
            for (TestDbInfo testDbInfo : testDbInfos) {
                DataSourceEntity sourceEntity = new DataSourceEntity();
                BeanUtils.copyProperties(testDbInfo,sourceEntity);
                sourceEntity.setKey(testDbInfo.getName());
                ds.add(sourceEntity);
            }
            dynamicDataSource.createDataSource(ds);
        }
    }
}

經(jīng)過(guò)上述SpringBoot啟動(dòng)后,已經(jīng)將數(shù)據(jù)庫(kù)表中的數(shù)據(jù)添加到動(dòng)態(tài)數(shù)據(jù)源中,我們調(diào)用之前的測(cè)試方法,將數(shù)據(jù)源名稱作為參數(shù)傳入看看執(zhí)行結(jié)果。

2.5.2.4 測(cè)試

通過(guò)測(cè)試我們發(fā)現(xiàn)數(shù)據(jù)庫(kù)表中的數(shù)據(jù)庫(kù)被動(dòng)態(tài)加入了數(shù)據(jù)源中,小伙伴可以愉快地隨意添加數(shù)據(jù)源了。

好了,今天就跟大家嘮叨到這,希望我的叨叨讓大家對(duì)于動(dòng)態(tài)切換數(shù)據(jù)源的方式能夠有更深地理解。

以上就是SpringBoot+ThreadLocal+AbstractRoutingDataSource實(shí)現(xiàn)動(dòng)態(tài)切換數(shù)據(jù)源的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot動(dòng)態(tài)切換數(shù)據(jù)源的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Java多線程的具體介紹與使用筆記小結(jié)

    Java多線程的具體介紹與使用筆記小結(jié)

    Java多線程詳細(xì)介紹線程是程序執(zhí)行的最小單元,多線程是指程序同一時(shí)間可以有多個(gè)執(zhí)行單元運(yùn)行(這個(gè)與你的CPU核心有關(guān))。 接下來(lái)通過(guò)本文給大家介紹Java多線程的具體介紹與使用筆記小結(jié),感興趣的朋友一起看看吧
    2021-05-05
  • 解決java項(xiàng)目jar打包后讀取文件失敗的問(wèn)題

    解決java項(xiàng)目jar打包后讀取文件失敗的問(wèn)題

    這篇文章主要介紹了解決java項(xiàng)目jar打包后讀取文件失敗的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-06-06
  • java實(shí)現(xiàn)Z字形掃描程序

    java實(shí)現(xiàn)Z字形掃描程序

    這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)Z字形掃描程序,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-03-03
  • MyBatis中selectKey標(biāo)簽及主鍵回填實(shí)現(xiàn)

    MyBatis中selectKey標(biāo)簽及主鍵回填實(shí)現(xiàn)

    <selectKey>標(biāo)簽在MyBatis中提供了一種靈活的方式來(lái)生成和回填主鍵,本文就來(lái)介紹一下selectKey標(biāo)簽及主鍵回填實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-12-12
  • SpringBoot 返回Json實(shí)體類屬性大小寫的解決

    SpringBoot 返回Json實(shí)體類屬性大小寫的解決

    這篇文章主要介紹了SpringBoot 返回Json實(shí)體類屬性大小寫的解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-10-10
  • java實(shí)現(xiàn)文件變化監(jiān)控的方法(推薦)

    java實(shí)現(xiàn)文件變化監(jiān)控的方法(推薦)

    下面小編就為大家?guī)?lái)一篇java實(shí)現(xiàn)文件變化監(jiān)控的方法(推薦)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2016-08-08
  • Java對(duì)象初始化順序的使用

    Java對(duì)象初始化順序的使用

    本篇文章介紹了,Java對(duì)象初始化順序的使用。需要的朋友參考下
    2013-04-04
  • 基于Java中UDP的廣播形式(實(shí)例講解)

    基于Java中UDP的廣播形式(實(shí)例講解)

    下面小編就為大家分享一篇基于Java中UDP的廣播形式(實(shí)例講解),具有很好的參考價(jià)值,希望對(duì)大家有所幫助
    2017-12-12
  • 基于TCP通信丟包原因總結(jié)(推薦)

    基于TCP通信丟包原因總結(jié)(推薦)

    下面小編就為大家?guī)?lái)一篇基于TCP通信丟包原因總結(jié)(推薦)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-08-08
  • Maven build 命令介紹的使用詳解

    Maven build 命令介紹的使用詳解

    這篇文章主要介紹了Maven build 命令介紹的使用詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-06-06

最新評(píng)論