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

Mybatis兩級(jí)緩存可能導(dǎo)致的問(wèn)題詳細(xì)講解

 更新時(shí)間:2025年07月16日 10:10:21   作者:趙丙雙  
MyBatis作為一款流行的持久層框架,在數(shù)據(jù)處理和緩存管理方面有著廣泛的應(yīng)用,其中,二級(jí)緩存作為其重要的緩存機(jī)制,對(duì)于提升應(yīng)用性能具有重要作用,但同時(shí)也存在一些潛在的問(wèn)題,這篇文章主要介紹了Mybatis兩級(jí)緩存可能導(dǎo)致問(wèn)題的相關(guān)資料,需要的朋友可以參考下

兩級(jí)緩存簡(jiǎn)介

一級(jí)緩存 localCache

效果

一級(jí)緩存是 session 或者說(shuō)事務(wù)級(jí)別的,只在同一事務(wù)內(nèi)有效,在以相同的參數(shù)執(zhí)行多次同一個(gè)查詢方法時(shí),實(shí)際只會(huì)在第一次時(shí)進(jìn)行數(shù)據(jù)庫(kù) select 查詢,后續(xù)會(huì)直接從緩存中返回。如下:

@GetMapping("/test1")
@Transactional(rollbackFor = Exception.class)
public String test1() {
    log.info("---------------------------------------------------------------------------");

    Teacher teacher1 = teacherMapper.selectByPrimaryKey("01");
    log.info("teacher1: {}, hashCode: {} \n", teacher1, System.identityHashCode(teacher1));

    Teacher teacher2 = teacherMapper.selectByPrimaryKey("01");
    log.info("teacher2: {}, hashCode: {} \n", teacher2, System.identityHashCode(teacher2));

    Student student1 = studentMapper.selectByPrimaryKey("01");
    log.info("student1: {}, hashCode: {} \n", student1, System.identityHashCode(student1));

    Student student2 = studentMapper.selectByPrimaryKey("01");
    log.info("student2: {}, hashCode: {} \n", student2, System.identityHashCode(student2));

    return "test1";
}

下圖中是調(diào)用了兩次的輸出,從第一次輸出中可以看出查詢 teacher、student 的 SQL 都只打印了一遍,說(shuō)明分別只執(zhí)行了一次數(shù)據(jù)庫(kù)查詢。且兩個(gè) teacher、student 的 hashCode 分別是一樣的,說(shuō)明是同一個(gè)對(duì)象。第二次調(diào)用的輸出和第一次的相似,都重新執(zhí)行了一次數(shù)據(jù)庫(kù)查詢,說(shuō)明一級(jí)緩存只在同一事務(wù)內(nèi)有效,不能跨事務(wù)。

如果事務(wù)中有 DML 語(yǔ)句的話,會(huì)清空所有的緩存。不管 DML 語(yǔ)句中的表是否與緩存中的表相同,都會(huì)無(wú)條件的清空所有緩存。

@GetMapping("/test2")
@Transactional(rollbackFor = Exception.class)
public String test2() {
    log.info("---------------------------------------------------------------------------");

    Teacher teacher1 = teacherMapper.selectByPrimaryKey("01");
    log.info("teacher1: {}, hashCode: {} \n", teacher1, System.identityHashCode(teacher1));

    Teacher teacher2 = teacherMapper.selectByPrimaryKey("01");
    log.info("teacher2: {}, hashCode: {} \n", teacher2, System.identityHashCode(teacher2));

    Student student1 = studentMapper.selectByPrimaryKey("01");
    log.info("student1: {}, hashCode: {} \n", student1, System.identityHashCode(student1));

    Student student2 = studentMapper.selectByPrimaryKey("01");
    log.info("student2: {}, hashCode: {} \n", student2, System.identityHashCode(student2));

    insertScore();
    log.info("insertScore\n");

    Teacher teacher3 = teacherMapper.selectByPrimaryKey("01");
    log.info("teacher3: {}, hashCode: {} \n", teacher3, System.identityHashCode(teacher3));

    Student student3 = studentMapper.selectByPrimaryKey("01");
    log.info("student3: {}, hashCode: {} \n", student3, System.identityHashCode(student3));

    return "test2";
}

private void insertScore() {
    Score score = new Score();
    score.setSId("08");
    score.setCId("01");
    score.setSScore(100);
    scoreMapper.insert(score);
}

前半部分的輸出與 test1 相同,當(dāng)插入 score 后再次查詢 teacher、student 時(shí),打印了 SQL,且與上半部分的 hashCode 不相同,說(shuō)明執(zhí)行 insertScore 時(shí)緩存被全部清空了。

開(kāi)關(guān)

一級(jí)緩存在 mybatis 源碼中被稱為 localCache,springboot 可使用 mybatis.configuration.local-cache-scope 來(lái)控制其行為,默認(rèn)值是 session,也就是事務(wù)級(jí)別的緩存??蓪⑵渑渲脼?statement 以關(guān)閉 localCache 功能。

下面是將 mybatis.configuration.local-cache-scope 配置為 statement 后再執(zhí)行 test1 的輸出,每次都打印了 SQL,且 hashCode 都不一樣,說(shuō)明緩存沒(méi)有起作用。

二級(jí)緩存

二級(jí)緩存是 namespace 級(jí)別的(或者說(shuō)是 Mapper 級(jí)別的,如下 xml),與一級(jí)緩存類似,在以相同的參數(shù)執(zhí)行多次同一個(gè)查詢方法時(shí),實(shí)際只會(huì)在第一次時(shí)進(jìn)行數(shù)據(jù)庫(kù) select 查詢,后續(xù)會(huì)直接從緩存中返回。如果執(zhí)行同一個(gè) namespace 中的 DML 語(yǔ)句(比如 delete、insert、update)的話,會(huì)清空 namespace 相關(guān)的所有 select 的緩存。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.example.mybatis.mapper.StudentMapper">
    <select>
        ...
    </select>
    <delete>
        ...
    </delete>
    <insert>
        ...
    </insert>
    ...
</mapper>

二級(jí)緩存由 mybatis.configuration.cache-enabled 控制,默認(rèn)為 true。除此之外還需要在要開(kāi)啟二級(jí)緩存的 Mapper.xml 中添加 <cache/> 表情才能開(kāi)啟對(duì)應(yīng) Mapper 的二級(jí)緩存。

下面是在關(guān)閉一級(jí)緩存,且只開(kāi)啟 StudentMapper.xml 二級(jí)緩存的情況下的測(cè)試:

application.properties

...
mybatis.configuration.local-cache-scope=statement
mybatis.configuration.cache-enabled=true

StudentMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.example.mybatis.mapper.StudentMapper">
  <resultMap id="BaseResultMap" type="org.example.mybatis.entity.Student">
    <!--@mbg.generated-->
    <!--@Table student-->
    <id column="s_id" jdbcType="VARCHAR" property="sId" />
    <result column="s_name" jdbcType="VARCHAR" property="sName" />
    <result column="s_birth" jdbcType="VARCHAR" property="sBirth" />
    <result column="s_sex" jdbcType="VARCHAR" property="sSex" />
  </resultMap>

  <cache readOnly="true"/>
  ...
</mapper>

這是執(zhí)行了兩次 test1 的輸出:

由于沒(méi)有開(kāi)啟 TeacherMapper.xml 的二級(jí)緩存,所以每次查詢 teacher 都打印了 SQL,且 hashCode 不相同,說(shuō)明 teacher 的緩存沒(méi)起作用。

第 ① 次查詢 student 打印了 SQL,直接查詢了數(shù)據(jù)庫(kù),這是正常的,因?yàn)榇藭r(shí)緩存中沒(méi)有數(shù)據(jù)。但第 ② 次查詢 student 也沒(méi)有走緩存,也直接查詢了數(shù)據(jù)庫(kù),這是為啥?是因?yàn)槎?jí)緩存不是在執(zhí)行完 select 后立即填充的,是要等到事務(wù)提交之后才會(huì)填充緩存。

從最后幾行的輸出能看出最后兩次查詢 student 確實(shí)走了緩存,并且還打印了緩存命中率。這是因?yàn)榈谝淮握{(diào)用 test1 結(jié)束后事務(wù)提交了,數(shù)據(jù)被填充到了緩存里。

測(cè)試無(wú)事務(wù)時(shí)的效果

test3 是在 test1 的基礎(chǔ)上刪除了 @Transactional 注解

@GetMapping("/test3")
public String test3() {
    log.info("---------------------------------------------------------------------------");

    Teacher teacher1 = teacherMapper.selectByPrimaryKey("01");
    log.info("teacher1: {}, hashCode: {} \n", teacher1, System.identityHashCode(teacher1));

    Teacher teacher2 = teacherMapper.selectByPrimaryKey("01");
    log.info("teacher2: {}, hashCode: {} \n", teacher2, System.identityHashCode(teacher2));

    Student student1 = studentMapper.selectByPrimaryKey("01");
    log.info("student1: {}, hashCode: {} \n", student1, System.identityHashCode(student1));

    Student student2 = studentMapper.selectByPrimaryKey("01");
    log.info("student2: {}, hashCode: {} \n", student2, System.identityHashCode(student2));

    return "test3";
}

teacher 的緩存還是沒(méi)起作用。

只有第一次查詢 student 時(shí)直接查詢了數(shù)據(jù)庫(kù),其他三次都命中了緩存。

兩級(jí)緩存可能導(dǎo)致的問(wèn)題

分布式環(huán)境下查詢到過(guò)期數(shù)據(jù)

假設(shè)支付服務(wù) A 有兩個(gè)實(shí)例 A1、A2,負(fù)載均衡采用輪訓(xùn)策略,第一次查詢余額訪問(wèn) A1 返回 100000,第二次消費(fèi) 100 訪問(wèn) A2 返回余額 99900,第三次查詢余額訪問(wèn) A1 返回的還是 100000。如下的模擬

application.properties

...
mybatis.configuration.local-cache-scope=statement
mybatis.configuration.cache-enabled=true

AccountMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.example.mybatis.mapper.AccountMapper">
  ...
  <cache readOnly="true"/>

  <update id="pay">
    update account
    set balance = balance - #{amount}
    where id = #{id}
  </update>
</mapper>
@GetMapping("/balance")
public Long queryBalance() {
    return accountMapper.selectByPrimaryKey(1).getBalance();
}

@GetMapping("/pay")
public Long pay() {
    accountMapper.pay(1, 100);
    return accountMapper.selectByPrimaryKey(1).getBalance();
}

分別在 8080、8081 啟動(dòng)兩個(gè)實(shí)例,如下輸出:

要解決這個(gè)問(wèn)題很簡(jiǎn)單,就是不使用緩存,比如 mybatis.configuration.cache-enabled=false 或者將 AccountMapper.xml 中的 <cache/> 標(biāo)簽刪除。

事務(wù)隔離級(jí)別失效

讀已提交失效

在開(kāi)發(fā)中經(jīng)常有這種場(chǎng)景:先判斷是否存在,如果不存在再插入。這種判斷再插入的操作不是原子的,多線程會(huì)有問(wèn)題,所以需要加鎖保證操作的安全性。在讀多寫(xiě)少的場(chǎng)景中,會(huì)使用 double check 來(lái)盡可能的減少用鎖的使用,偽代碼如下:

def doubleCheck(id) {
    o = select(id);
    if (o == null) {
        lock.lock();
        try {
            o = select(id);
            if (o == null) {
                o = create(id);
            }
        } finally {
            lock.unlock();
        }
    }
    return o;
}

創(chuàng)建 Account 的測(cè)試

application.properties

還原成默認(rèn)值,且刪除 AccountMapper.xml 中的 <cache/> 標(biāo)簽,用以關(guān)閉 AccountMapper 的二級(jí)緩存。

...
mybatis.configuration.local-cache-scope=session
mybatis.configuration.cache-enabled=true

注意這里使用的隔離級(jí)別為讀已提交

@PutMapping("/accounts/{id}")
// double check 需要使用讀已提交隔離級(jí)別才能讀到最新數(shù)據(jù)
@Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_COMMITTED)
public Account createAccount(@PathVariable("id") Integer id) throws InterruptedException {
    Account account = accountMapper.selectByPrimaryKey(id);

    // 等待多個(gè)請(qǐng)求到達(dá)
    TimeUnit.SECONDS.sleep(5);

    // 如果賬戶不存在,需要加分布式鎖后進(jìn)行 double check,防止并發(fā)問(wèn)題
    if (account == null) {
        RLock lock = redissonClient.getLock("lock:account:create:" + id);
        boolean locked = lock.tryLock(10, TimeUnit.SECONDS);
        if (locked) {
            try {
                account = accountMapper.selectByPrimaryKey(id);
                if (account == null) {
                    // 創(chuàng)建賬戶
                    account = createAccount0(id);
                }
            } finally {
                lock.unlock();
            }
        }
    }
    return account;
}

public Account createAccount0(Integer id) {
    Account account = new Account();
    account.setId(id);
    account.setBalance(0L);
    accountMapper.insertSelective(account);
    
    // 操作其他表
    return account;
}

同時(shí)發(fā)起兩個(gè) Put 請(qǐng)求 http://localhost:8080/accounts/2。一個(gè)正常返回,另一個(gè)在 insert 時(shí)報(bào)錯(cuò) Duplicate entry ‘2’ for key ‘account.PRIMARY’,說(shuō)明讀已提交的隔離級(jí)別沒(méi)起作用,第二個(gè)請(qǐng)求沒(méi)有讀到最新的數(shù)據(jù)。

一級(jí)緩存實(shí)際起到了類似可重復(fù)讀的效果。

兩個(gè)請(qǐng)求(線程分別為 nio-8080-exec-3、nio-8080-exec-4)執(zhí)行了 3 次(第一個(gè)請(qǐng)求 1 次,第二個(gè)請(qǐng)求 2 次) accountMapper.selectByPrimaryKey(id),但每個(gè)線程都只打印了 1 次 SQL,說(shuō)明第二個(gè)請(qǐng)求的第 2 次查詢走了緩存,導(dǎo)致沒(méi)有查詢到第一個(gè)請(qǐng)求插入的最新數(shù)據(jù),才導(dǎo)致的后來(lái)的報(bào)錯(cuò)。

解決辦法

  1. 最簡(jiǎn)單辦法就是修改 mybatis.configuration.local-cache-scope=statement,直接關(guān)閉一級(jí)緩存。

  2. 直接去掉 @Transactional 注解肯定能解決問(wèn)題,但如果 createAccount0 方法中操作多張表的話,如果部分失敗事務(wù)將無(wú)法回滾。

  3. 不能直接去掉 @Transactional 注解,但可以縮小事務(wù)的范圍,將兩次查詢放到事務(wù)外,只將 createAccount0 方法放到事務(wù)內(nèi)。

    @Lazy
    @Autowired
    private TestController self;
    
    @PutMapping("/accounts/{id}")
    public Account createAccount(@PathVariable("id") Integer id) throws InterruptedException {
        Account account = accountMapper.selectByPrimaryKey(id);
    
        // 等待多個(gè)請(qǐng)求到達(dá)
        TimeUnit.SECONDS.sleep(5);
    
        // 如果賬戶不存在,需要加分布式鎖后進(jìn)行 double check,防止并發(fā)問(wèn)題
        if (account == null) {
            RLock lock = redissonClient.getLock("lock:account:create:" + id);
            boolean locked = lock.tryLock(10, TimeUnit.SECONDS);
            if (locked) {
                try {
                    account = accountMapper.selectByPrimaryKey(id);
                    if (account == null) {
                        // 創(chuàng)建賬戶
                        account = self.createAccount0(id);
                    }
                } finally {
                    lock.unlock();
                }
            }
        }
        return account;
    }
    
    @Transactional(rollbackFor = Exception.class)
    public Account createAccount0(Integer id) {
        Account account = new Account();
        account.setId(id);
        account.setBalance(0L);
        accountMapper.insertSelective(account);
    
        // 操作其他表
        return account;
    }
    
  4. 如果外層有其他事務(wù)的話,由于一級(jí)緩存只有在同一個(gè)事務(wù)中才會(huì)生效,所以可以將兩個(gè) accountMapper.selectByPrimaryKey(id) 拆分到不同的事務(wù)中,propagation 必須是 Propagation.REQUIRES_NEW。

    @Lazy
    @Autowired
    private TestController self;
    
    @PutMapping("/accounts/{id}")
    public Account createAccount(@PathVariable("id") Integer id) throws InterruptedException {
        Account account = self.getAccount0(id);
    
        // 等待多個(gè)請(qǐng)求到達(dá)
        TimeUnit.SECONDS.sleep(5);
    
        // 如果賬戶不存在,需要加分布式鎖后進(jìn)行 double check,防止并發(fā)問(wèn)題
        if (account == null) {
            RLock lock = redissonClient.getLock("lock:account:create:" + id);
            boolean locked = lock.tryLock(10, TimeUnit.SECONDS);
            if (locked) {
                try {
                    account = self.getAccount0(id);
                    if (account == null) {
                        // 創(chuàng)建賬戶
                        //
                        account = self.createAccount0(id);
                    }
                } finally {
                    lock.unlock();
                }
            }
        }
        return account;
    }
    
    // 讀已提交 REQUIRES_NEW
    @Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRES_NEW)
    public Account getAccount0(Integer id) {
        return accountMapper.selectByPrimaryKey(id);
    }
    

讀未提交失效

同樣的由于一級(jí)緩存的存在,讀未提交也讀不到最新的未提交數(shù)據(jù)。

讀未提交 查詢 Account 的測(cè)試

application.properties

還原成默認(rèn)值,且刪除 AccountMapper.xml 中的 <cache/> 標(biāo)簽,用以關(guān)閉 AccountMapper 的二級(jí)緩存。

...
mybatis.configuration.local-cache-scope=session
mybatis.configuration.cache-enabled=true
@GetMapping("/accounts/{id}")
// 讀未提交
@Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_UNCOMMITTED)
public Account getAccount(@PathVariable("id") Integer id) throws InterruptedException {
    Account account = accountMapper.selectByPrimaryKey(id);
    log.info("account1: {}\n", account);

    // 若不存在,則等待幾秒再查
    if (account == null) {
        TimeUnit.SECONDS.sleep(10);
    }
    account = accountMapper.selectByPrimaryKey(id);
    log.info("account2: {}\n", account);
    return account;
}

@PutMapping("/accounts/{id}")
@Transactional(rollbackFor = Exception.class)
public Account createAccount(@PathVariable("id") Integer id) throws InterruptedException {

    Account account = new Account();
    account.setId(id);
    account.setBalance(0L);
    accountMapper.insertSelective(account);
    log.info("insert account: {}\n", account);

    // 延遲提交事務(wù)
    TimeUnit.SECONDS.sleep(15);

    // 操作其他表
    return account;
}

先請(qǐng)求 getAccount 再請(qǐng)求 createAccount,從輸出中可以看出,在使用讀未提交的情況下,account2 依舊為 null,走了緩存,導(dǎo)致讀未提交失效。

解決辦法

  1. 最簡(jiǎn)單辦法就是修改 mybatis.configuration.local-cache-scope=statement,直接關(guān)閉一級(jí)緩存。

  2. 由于一級(jí)緩存只有在同一個(gè)事務(wù)中才會(huì)生效,所以可以將兩個(gè) accountMapper.selectByPrimaryKey(id) 拆分到不同的事務(wù)中,propagation 必須是 Propagation.REQUIRES_NEW。

    @Lazy
    @Autowired
    private TestController self;
    
    @GetMapping("/accounts/{id}")
    public Account getAccount(@PathVariable("id") Integer id) throws InterruptedException {
        Account account = self.getAccount0(id);
        log.info("account1: {}\n", account);
    
        // 若不存在,則等待幾秒再查
        if (account == null) {
            TimeUnit.SECONDS.sleep(10);
        }
        account = self.getAccount0(id);
        log.info("account2: {}\n", account);
        return account;
    }
    
    // 讀未提交 REQUIRES_NEW
    @Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_UNCOMMITTED, propagation = Propagation.REQUIRES_NEW)
    public Account getAccount0(Integer id) {
        return accountMapper.selectByPrimaryKey(id);
    }
    

總結(jié)

一級(jí)緩存是事務(wù)級(jí)別的,實(shí)際起到了類似可重復(fù)讀的效果,而且比可重復(fù)讀的性能更好,因?yàn)槎啻尾樵兊脑挷粫?huì)請(qǐng)求數(shù)據(jù)庫(kù)了。在事務(wù)隔離級(jí)別是可重復(fù)讀時(shí)使用一級(jí)緩存能提高性能。但就因?yàn)槠漕愃瓶芍貜?fù)讀的效果會(huì)導(dǎo)致其他的隔離級(jí)別失效。要解決失效的問(wèn)題,最簡(jiǎn)單方式就是關(guān)閉一級(jí)緩存,但這樣會(huì)損失性能。另一個(gè)解決辦法是將需要使用其他隔離級(jí)別的方法使用 propagation = Propagation.REQUIRES_NEW 拆分到新的事務(wù)中。如果是讀已提交的話可通過(guò)縮小事務(wù)范圍的方式解決。

一級(jí)緩存是事務(wù)級(jí)別的,緩存的生命周期較短,但二級(jí)緩存是 namespace (Mapper)級(jí)別的,生命周期可能很長(zhǎng),在分布式、多實(shí)例環(huán)境中很容易查詢到過(guò)期的數(shù)據(jù),導(dǎo)致其他問(wèn)題。我個(gè)人建議在分布式、多實(shí)例環(huán)境中應(yīng)該設(shè)置 mybatis.configuration.cache-enabled=false 來(lái)關(guān)閉二級(jí)緩存,從根源上杜絕這種問(wèn)題。

到此這篇關(guān)于Mybatis兩級(jí)緩存可能導(dǎo)致問(wèn)題的文章就介紹到這了,更多相關(guān)Mybatis兩級(jí)緩存問(wèn)題內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java自動(dòng)拆裝箱簡(jiǎn)單介紹

    Java自動(dòng)拆裝箱簡(jiǎn)單介紹

    這篇文章主要為大家詳細(xì)介紹了Java自動(dòng)拆裝箱的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-10-10
  • Java字節(jié)碼中jvm實(shí)例用法

    Java字節(jié)碼中jvm實(shí)例用法

    在本篇文章里小編給大家整理的是一篇關(guān)于Java字節(jié)碼中jvm實(shí)例用法內(nèi)容,有興趣的朋友們可以學(xué)習(xí)參考下。
    2021-02-02
  • Spring boot通過(guò)AOP防止API重復(fù)請(qǐng)求代碼實(shí)例

    Spring boot通過(guò)AOP防止API重復(fù)請(qǐng)求代碼實(shí)例

    這篇文章主要介紹了Spring boot通過(guò)AOP防止API重復(fù)請(qǐng)求代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-12-12
  • springboot如何使用thymeleaf模板訪問(wèn)html頁(yè)面

    springboot如何使用thymeleaf模板訪問(wèn)html頁(yè)面

    springboot中推薦使用thymeleaf模板,使用html作為頁(yè)面展示。那么如何通過(guò)Controller來(lái)訪問(wèn)來(lái)訪問(wèn)html頁(yè)面呢?下面通過(guò)本文給大家詳細(xì)介紹,感興趣的朋友跟隨腳本之家小編一起看看吧
    2018-05-05
  • 2018版java多線程面試題集合及答案

    2018版java多線程面試題集合及答案

    這篇文章主要為大家詳細(xì)介紹了2018版java多線程面試題集合及答案,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-09-09
  • 超詳細(xì)講解SpringBoot參數(shù)校驗(yàn)實(shí)例

    超詳細(xì)講解SpringBoot參數(shù)校驗(yàn)實(shí)例

    經(jīng)常需要提供接口與用戶交互(獲取數(shù)據(jù)、上傳數(shù)據(jù)等),由于這個(gè)過(guò)程需要用戶進(jìn)行相關(guān)的操作,為了避免出現(xiàn)一些錯(cuò)誤的數(shù)據(jù)等,一般需要對(duì)數(shù)據(jù)進(jìn)行校驗(yàn),下面這篇文章主要給大家介紹了關(guān)于SpringBoot各種參數(shù)校驗(yàn)的相關(guān)資料,需要的朋友可以參考下
    2022-05-05
  • Java?8?Time?Api?使用方法技巧

    Java?8?Time?Api?使用方法技巧

    這篇文章主要介紹了Java?8?Time?Api?使用方法技巧,Java?8為Date和Time引入了新的API,以解決舊java.util.Date和java.util.Calendar的缺點(diǎn),更多相關(guān)內(nèi)容需要的小伙伴可以參考一下
    2022-05-05
  • SpringBoot開(kāi)發(fā)案例 分布式集群共享Session詳解

    SpringBoot開(kāi)發(fā)案例 分布式集群共享Session詳解

    這篇文章主要介紹了SpringBoot開(kāi)發(fā)案例 分布式集群共享Session詳解,在分布式系統(tǒng)中,為了提升系統(tǒng)性能,通常會(huì)對(duì)單體項(xiàng)目進(jìn)行拆分,分解成多個(gè)基于功能的微服務(wù),可能還會(huì)對(duì)單個(gè)微服務(wù)進(jìn)行水平擴(kuò)展,保證服務(wù)高可用,需要的朋友可以參考下
    2019-07-07
  • 使用mybatis-plus的insert方法遇到的問(wèn)題及解決方法(添加時(shí)id值不存在異常)

    使用mybatis-plus的insert方法遇到的問(wèn)題及解決方法(添加時(shí)id值不存在異常)

    這篇文章主要介紹了使用mybatis-plus的insert方法遇到的問(wèn)題及解決方法(添加時(shí)id值不存在異常),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-08-08
  • Java 互相關(guān)聯(lián)的實(shí)體無(wú)限遞歸問(wèn)題的解決

    Java 互相關(guān)聯(lián)的實(shí)體無(wú)限遞歸問(wèn)題的解決

    這篇文章主要介紹了Java 互相關(guān)聯(lián)的實(shí)體無(wú)限遞歸問(wèn)題的解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-10-10

最新評(píng)論