最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Spring多數(shù)據(jù)源切換失敗,發(fā)現(xiàn)與事務(wù)相關(guān)問(wèn)題

 更新時(shí)間:2024年01月31日 14:18:59   作者:ChengQinHong  
這篇文章主要介紹了Spring多數(shù)據(jù)源切換失敗,發(fā)現(xiàn)與事務(wù)相關(guān)問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教

背景

一個(gè)方法里,A數(shù)據(jù)源需要進(jìn)行查詢和更新操作,B數(shù)據(jù)源進(jìn)行查詢操作。

詳細(xì)業(yè)務(wù)是查詢用戶基礎(chǔ)信息數(shù)據(jù)(A數(shù)據(jù)源)的時(shí)候,需要查詢登錄行為數(shù)據(jù)(B數(shù)據(jù)源),同時(shí)對(duì)用戶信息進(jìn)行修改,所以該方法最后還需要更新用戶基礎(chǔ)信息數(shù)據(jù)(A數(shù)據(jù)源)。

原來(lái)的A實(shí)現(xiàn)思路

controller調(diào)用serviceImpl方法,在serviceImpl方法內(nèi)某一段代碼去切換B數(shù)據(jù)源查詢結(jié)果,根據(jù)查詢結(jié)果更新A數(shù)據(jù)源。

實(shí)現(xiàn)A思路的結(jié)果

表面上一切調(diào)試都顯示切換了B數(shù)據(jù)源,應(yīng)該是還沒(méi)有深入源碼去debug、

應(yīng)該某個(gè)地方拿的數(shù)據(jù)源連接還是默認(rèn)的A數(shù)據(jù)源,導(dǎo)致報(bào)錯(cuò),提示沒(méi)有找到對(duì)應(yīng)數(shù)據(jù)庫(kù)表,A數(shù)據(jù)源肯定沒(méi)有B數(shù)據(jù)源的表啊,郁悶。

后來(lái)的B實(shí)現(xiàn)思路

查詢帖子后發(fā)現(xiàn)事務(wù)的干預(yù),導(dǎo)致數(shù)據(jù)源切換失敗,決定換個(gè)思路,把切換數(shù)據(jù)源的方法放在了controller,因?yàn)槲沂莾H對(duì)一個(gè)數(shù)據(jù)源進(jìn)行更新操作,另一個(gè)數(shù)據(jù)源只作查詢操作,此時(shí)整個(gè)事務(wù)其實(shí)只在A數(shù)據(jù)源進(jìn)行,所以我就單獨(dú)把對(duì)A數(shù)據(jù)源的操作聲明為A方法,對(duì)B數(shù)據(jù)源的操作聲明為B方法,在controller先調(diào)用B方法獲取查詢結(jié)果,作為入?yún)⑷フ{(diào)用A方法,這樣就解決了數(shù)據(jù)源切換問(wèn)題也解決了A方法的事務(wù)問(wèn)題。

實(shí)現(xiàn)B思路的結(jié)果

達(dá)到了預(yù)期。

切換數(shù)據(jù)源成功,A數(shù)據(jù)源查詢、更新、事務(wù)都沒(méi)問(wèn)題,B數(shù)據(jù)源查詢沒(méi)問(wèn)題。

??注意:

此思路是把service的方法一分為二,在controller分別調(diào)用,只適用于對(duì)其中單一數(shù)據(jù)源作修改數(shù)據(jù)操作,并不適用于對(duì)多數(shù)據(jù)源同時(shí)進(jìn)行修改數(shù)據(jù)操作,因?yàn)閱螖?shù)據(jù)源進(jìn)行數(shù)據(jù)操作是普通數(shù)據(jù)源事務(wù),并不復(fù)雜,就和我們平時(shí)使用@Transactional一樣。

但是如果你對(duì)多數(shù)據(jù)源進(jìn)行修改數(shù)據(jù)操作的話!事情就變得復(fù)雜起來(lái)了,多數(shù)據(jù)源事務(wù),可讓你頭疼的了,因?yàn)榛貪L非常麻煩,類似于分布式事務(wù)了,阿里的分布式事務(wù)有SEATA支撐,這個(gè)我了解,但是以后我再講這方面的,因?yàn)檫@個(gè)單體系統(tǒng)的多數(shù)據(jù)源事務(wù)還需要深入研究一下。

原因

由于默認(rèn)使用的是主數(shù)據(jù)源master,只有在mapper接口方法上標(biāo)注從數(shù)據(jù)源slave才會(huì)切換數(shù)據(jù)源過(guò)去,但是要注意事務(wù)(因?yàn)橹翱匆粋€(gè)帖子,說(shuō)一個(gè)事物里,緩存了默認(rèn)的數(shù)據(jù)庫(kù)連接,即使代碼里切換了數(shù)據(jù)源,重新去建立連接時(shí)候發(fā)現(xiàn)有緩存一個(gè)數(shù)據(jù)庫(kù)連接耶,直接拿這個(gè),導(dǎo)致我們切換數(shù)據(jù)源失敗,因?yàn)槟玫倪€是默認(rèn)的數(shù)據(jù)庫(kù)連接。

配置文件

spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driverClassName: com.mysql.jdbc.Driver
    master: #主庫(kù)A
      type: com.alibaba.druid.pool.DruidDataSource
      driverClassName: com.mysql.jdbc.Driver
      url: jdbc:mysql://127.0.0.1:3306/A?useUnicode=true&characterEncoding=UTF-8&useOldAliasMetadataBehavior=true&useSSL=false&serverTimezone=GMT%2B8
      username: root
      password: 123456
    slave: #從庫(kù)B
      type: com.alibaba.druid.pool.DruidDataSource
      driverClassName: com.mysql.jdbc.Driver
      url: jdbc:mysql://192.168.1.12:3306/B?useUnicode=true&characterEncoding=UTF-8&useOldAliasMetadataBehavior=true&useSSL=true&serverTimezone=GMT%2B8
      username: root
      password: 123456

配置類(蠻多的,注意,請(qǐng)復(fù)制完整)

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy;
 
import javax.sql.DataSource;
import java.util.HashMap;
 
// 主從數(shù)據(jù)源配置
@Configuration
public class DataSourceConfiguration {
 
    DataSourceProperties masterDataSource = new DataSourceProperties();
    DataSourceProperties slaveDataSource = new DataSourceProperties();
 
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.master")
    public DataSource masterDataSource() {
        DruidDataSource druidDataSource = masterDataSource.setDataSource(DruidDataSourceBuilder.create().build());
        return druidDataSource;
    }
 
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.slave")
    public DataSource slaveDataSource() {
        DruidDataSource druidDataSource = slaveDataSource.setDataSource(DruidDataSourceBuilder.create().build());
        return druidDataSource;
    }
 
    @Bean
    public DataSource routeDataSource() {
        RoutingDataSource routingDataSource = new RoutingDataSource() {{
            setDefaultTargetDataSource(masterDataSource());
            setTargetDataSources(new HashMap<Object, Object>() {{
                put(DbType.MASTER, masterDataSource());
                put(DbType.SLAVE, slaveDataSource());
            }});
        }};
 
        return routingDataSource;
    }
 
    @Bean
    @Primary
    public LazyConnectionDataSourceProxy lazyConnectionDataSourceProxy() {
        return new LazyConnectionDataSourceProxy(routeDataSource());
    }
 
}
import com.alibaba.druid.pool.DruidDataSource;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
 
/**
 * 數(shù)據(jù)源配置文件
 */
@Setter
@Configuration
@ConfigurationProperties(prefix = "spring.datasource.druid")
public class DataSourceProperties {
 
    private int initialSize = 10;
 
    private int minIdle = 10;
 
    private int maxActive = 50;
 
    private int maxWait;
 
    private int timeBetweenEvictionRunsMillis = 300000;
 
    private int minEvictableIdleTimeMillis = 60000;
 
    private int maxEvictableIdleTimeMillis = 7200000;
 
    private String validationQuery = "SELECT 1 FROM DUAL";
 
    private boolean testWhileIdle = true;
 
    private boolean testOnBorrow = true;
 
    private boolean testOnReturn = true;
 
    public DruidDataSource setDataSource(DruidDataSource datasource) {
        /** 配置初始化大小、最小、最大 */
        datasource.setInitialSize(initialSize);  //優(yōu)先級(jí):application的spring.datasource.master.initialSize > application的spring.datasource.druid.initialSize > datasource.setInitialSize(20)和datasource.setInitialSize(initialSize)
        datasource.setMaxActive(maxActive);
        datasource.setMinIdle(minIdle);
        /** 配置獲取連接等待超時(shí)的時(shí)間 */
        // datasource.setMaxWait(maxWait);
        /** 配置間隔多久才進(jìn)行一次檢測(cè),檢測(cè)需要關(guān)閉的空閑連接,單位是毫秒 */
        datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
        /** 配置一個(gè)連接在池中最小、最大生存的時(shí)間,單位是毫秒 */
        datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
        datasource.setMaxEvictableIdleTimeMillis(maxEvictableIdleTimeMillis);
        /**
         * 用來(lái)檢測(cè)連接是否有效的sql,要求是一個(gè)查詢語(yǔ)句,常用select 'x'。如果validationQuery為null,testOnBorrow、testOnReturn、testWhileIdle都不會(huì)起作用。
         */
        datasource.setValidationQuery(validationQuery);
        /** 建議配置為true,不影響性能,并且保證安全性。申請(qǐng)連接的時(shí)候檢測(cè),如果空閑時(shí)間大于timeBetweenEvictionRunsMillis,執(zhí)行validationQuery檢測(cè)連接是否有效。 */
        datasource.setTestWhileIdle(testWhileIdle);
        /** 申請(qǐng)連接時(shí)執(zhí)行validationQuery檢測(cè)連接是否有效,做了這個(gè)配置會(huì)降低性能。 */
        datasource.setTestOnBorrow(testOnBorrow);
        /** 歸還連接時(shí)執(zhí)行validationQuery檢測(cè)連接是否有效,做了這個(gè)配置會(huì)降低性能。 */
        datasource.setTestOnReturn(testOnReturn);
        
        return datasource;
    }
 
}
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
 
// aop環(huán)繞增強(qiáng)切換數(shù)據(jù)源
@Slf4j
@Aspect
@Component
public class DBConnectionAOP {
 
    @Around("@annotation(connectToDB)")
    public Object proceed(ProceedingJoinPoint pjp, ConnectToDB connectToDB) throws Throwable {
        try {
            if (connectToDB.value().equals("MASTER")) {
                log.info("Master DB 配置");
                DBContextHolder.setDbType(DbType.MASTER);
            } else if (connectToDB.value().equals("SLAVE")) {
                log.info("Slave DB 配置");
                DBContextHolder.setDbType(DbType.SLAVE);
            } else {
                log.info("默認(rèn) DB 配置");
            }
 
            Object result = pjp.proceed();
            DBContextHolder.clearDbType();
 
            return result;
        } finally {
            DBContextHolder.clearDbType();
        }
    }
}
public class DBContextHolder {
 
    private static final ThreadLocal<DbType> contextHolder = new ThreadLocal<DbType>();
 
    public static void setDbType(DbType dbType) {
        if (dbType == null) {
            throw new NullPointerException();
        }
        contextHolder.set(dbType);
    }
 
    public static DbType getDbType() {
        return (DbType) contextHolder.get();
    }
 
    public static void clearDbType() {
        contextHolder.remove();
    }
 
}
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
 
// 動(dòng)態(tài)數(shù)據(jù)源路由配置
public class RoutingDataSource extends AbstractRoutingDataSource {
 
    // 決定使用哪個(gè)數(shù)據(jù)源
    @Override
    protected Object determineCurrentLookupKey() {
        return DBContextHolder.getDbType();
    }
 
}
// 數(shù)據(jù)源枚舉
public enum DbType {
    MASTER,
    SLAVE
}
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
 
// 數(shù)據(jù)源選擇注解
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ConnectToDB {
    // 默認(rèn)主數(shù)據(jù)庫(kù)
    String value() default "primary";
 
}

異步線程池配置

如果有異步需求的話,可以借鑒,所以也放上來(lái)吧。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
 
/**
 * 異步線程池的配置類
 */
@Configuration
@EnableAsync
public class ExecutorConfig {
    private static final Logger logger = LoggerFactory.getLogger(ExecutorConfig.class);
 
    @Bean
    public Executor asyncServiceExecutor() {
        logger.info("注冊(cè)asyncServiceExecutor");
 
//        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        ThreadPoolTaskExecutor executor = new VisibleThreadPoolTaskExecutor();
        //配置核心線程數(shù)
        executor.setCorePoolSize(5);
        //配置最大線程數(shù)
        executor.setMaxPoolSize(50);
        //配置隊(duì)列大小
        // Set the capacity for the ThreadPoolExecutor's BlockingQueue. Default is Integer.MAX_VALUE.
        // Any positive value will lead to a LinkedBlockingQueue instance; any other value will lead to a SynchronousQueue instance.
        executor.setQueueCapacity(100);
        // 設(shè)置允許的空閑時(shí)間(秒)
        executor.setKeepAliveSeconds(60);
        //配置線程池中的線程的名稱前綴
        executor.setThreadNamePrefix("async-service-");
        // rejection-policy:當(dāng)pool已經(jīng)達(dá)到max size的時(shí)候,如何處理新任務(wù);CALLER_RUNS:不在新線程中執(zhí)行任務(wù),而是有調(diào)用者所在的線程來(lái)執(zhí)行
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy());
        //執(zhí)行初始化
        executor.initialize();
 
        return executor;
    }
 
 
}
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.util.concurrent.ListenableFuture;
 
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;
 
/**
 * ThreadPoolTaskExecutor的子類,在父類的基礎(chǔ)上加入了日志信息,查看線程池的信息
 */
public class VisibleThreadPoolTaskExecutor extends ThreadPoolTaskExecutor {
    private static final Logger logger = LoggerFactory.getLogger(VisibleThreadPoolTaskExecutor.class);
 
    @Override
    public void execute(Runnable task) {
        showThreadPoolInfo("1. do execute");
        super.execute(task);
    }
 
    @Override
    public void execute(Runnable task, long startTimeout) {
        showThreadPoolInfo("2. do execute");
        super.execute(task, startTimeout);
    }
 
    @Override
    public Future<?> submit(Runnable task) {
        showThreadPoolInfo("1. do submit");
        return super.submit(task);
    }
 
    @Override
    public <T> Future<T> submit(Callable<T> task) {
        showThreadPoolInfo("2. do submit");
        return super.submit(task);
    }
 
    @Override
    public ListenableFuture<?> submitListenable(Runnable task) {
        showThreadPoolInfo("1. do submitListenable");
        return super.submitListenable(task);
    }
 
    @Override
    public <T> ListenableFuture<T> submitListenable(Callable<T> task) {
        showThreadPoolInfo("2. do submitListenable");
        return super.submitListenable(task);
    }
 
    private void showThreadPoolInfo(String prefix) {
        ThreadPoolExecutor executor = getThreadPoolExecutor();
 
        if (null == executor) {
            return;
        }
 
        logger.info("MaximumPoolSize:" + executor.getMaximumPoolSize() +
                ",CorePoolSize:" + executor.getCorePoolSize() +
                ",ThreadNamePrefix:" + this.getThreadNamePrefix() +
                ",prefix:" + prefix +
                ",TaskCount:" + executor.getTaskCount() +
                ",CompletedTaskCount:" + executor.getCompletedTaskCount() +
                ",ActiveCount:" + executor.getActiveCount() +
                ",PoolSize:" + executor.getPoolSize() +
                ",QueueSize:" + executor.getQueue().size()
        );
    }
 
}

A思路業(yè)務(wù)代碼(反面教材,請(qǐng)勿模仿)

controller

@PostMapping("queryUserData.do")
    @VerifyLogin
    public RetInfo<Object> queryUserData(String temp, String channelId) throws JsonProcessingException {
        String mobile = UserUtil.getMobile();
        User user = userService.queryUserData(mobile, temp, channelId);
        return new RetInfo<>(RetEnum.SUCCESS.getCode(), RetEnum.SUCCESS.getMsg(), user);
    }
@Slf4j
@Service
@Transactional(rollbackFor = Exception.class)
public class UserServiceImpl implements UserService {
    @Autowired
    private UserLogService userLogService;
 
    @Override
    public User queryUserData(String mobile, String temp, String channelId) throws JsonProcessingException {
        // 切換slave從數(shù)據(jù)源查詢
        Date loginTime = userLogService.validateLoginByToday(mobile); // 查詢當(dāng)天登錄時(shí)間
 
        Date date = new Date();
        Integer userStatus = 0;
        
        // 以下所有操作都切換回master主數(shù)據(jù)源查詢和更新
        User user = userMapper.selectUserByMobile(mobile);
        // 業(yè)務(wù)代碼.....
        userMapper.insert(user);
}
}

B思路業(yè)務(wù)代碼(正確!有相同業(yè)務(wù)場(chǎng)景的可以借鑒)

controller

    @PostMapping("queryUserData.do")
    public RetInfo<Object> queryUserData(String temp, String channelId) throws JsonProcessingException {
        long start = System.currentTimeMillis();
        String mobile = UserUtil.getMobile();
        HashMap<String, Object> map = new HashMap<>();
        // 切換slave從數(shù)據(jù)源查詢
        Date loginTime = userLogService.validateLoginByToday(mobile); // 查詢用戶當(dāng)天登錄時(shí)間
        // 切換回master主數(shù)據(jù)源查詢和更新
        User user = userService.queryUserData(mobile, loginTime, temp, channelId);
 
        return new RetInfo<>(RetEnum.SUCCESS.getCode(), RetEnum.SUCCESS.getMsg(), user);
    }

service 只是貼出了從數(shù)據(jù)源查詢代碼,主數(shù)據(jù)源的代碼就和平常寫(xiě)的一樣就行了。

@Service
public class UserLogServiceImpl implements UserLogService {
 
    private UserBehaviorService userBehaviorService;
 
    public UserLogServiceImpl(UserBehaviorService userBehaviorService) {
        this.userBehaviorService = userBehaviorService;
    }
 
    @Override
    public Date validateLoginByToday(String mobile) {
        return userBehaviorService.validateLoginByToday(mobile);
    }
}
@Slf4j
@Service
public class UserBehaviorServiceImpl implements UserBehaviorService {
 
    private UserBehaviorMapper userBehaviorMapper;
 
    public UserBehaviorServiceImpl(UserBehaviorMapper userBehaviorMapper) {
        this.userBehaviorMapper = userBehaviorMapper;
    }
 
    @Transactional
    @ConnectToDB(value = "SLAVE") // 這就是切換數(shù)據(jù)源最重要注解
    @Override
    public Date validateLoginByToday(String mobile) {
            return userBehaviorMapper.validateLoginByToday(mobile, DateUtil.dateToString(new Date(), "yyyyMMdd"));
    }
}

mapper(和平常一樣,沒(méi)有什么特別的)

@Mapper
public interface UserBehaviorMapper {
    /**
     * 查詢用戶當(dāng)天是否活躍
     * @param mobile
     * @param dateStr
     * @return Integer
     */
    Integer validateActive(@Param("mobile") String mobile, @Param("dateStr") String dateStr);
}

總結(jié)

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

最新評(píng)論

靖边县| 佛山市| 双牌县| 蒙城县| 鹤山市| 金寨县| 刚察县| 阜城县| 汝南县| 浦江县| 安福县| 高平市| 台中市| 原阳县| 东兴市| 玉山县| 昭通市| 剑河县| 荔浦县| 迁西县| 江门市| 湘阴县| 铁岭县| 巴南区| 三亚市| 垣曲县| 略阳县| 河池市| 饶阳县| 德化县| 色达县| 凯里市| 县级市| 利辛县| 淳化县| 张掖市| 晋州市| 淮安市| 固镇县| 汝州市| 阳春市|