1.項目目錄結構:
2.pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.cxb.datasources</groupId>
<artifactId>springboot-datasources</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>springboot-datasources</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.2.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- web核心組件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- mybatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.0</version>
</dependency>
<!-- mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
<pluginRepository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</project>
3.mybatis-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 開啓自動映射java駝峯 -->
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
</configuration>
4.application.yml
##多數據源
datasource:
#主庫
master:
jdbcUrl: jdbc:mysql://localhost:3306/master_base?useUnicode=true&characterEncoding=utf-8
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
#從庫
slave:
#並非url而是jdbcUrl(因爲這個在獲取數據源時一直報錯,看了DataSource的屬性才知道是jdbcUrl)
jdbcUrl: jdbc:mysql://localhost:3306/slave_base?useUnicode=true&characterEncoding=utf-8
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
##mybatis
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.cxb.database.domain
check-config-location: true
config-location: classpath:mybatis-config.xml
5.TestMapper.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="com.cxb.database.mapper.TestMapper">
<select id="queryCount" resultType="Integer">
SELECT count(1) FROM t_user
</select>
</mapper>
6.TestController
package com.cxb.database.controller;
import com.cxb.database.service.TestService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestController {
protected static final Logger logger = LoggerFactory.getLogger(TestController.class);
@Autowired
private TestService testService;
@RequestMapping("/test")
public String index() {
logger.debug("測試信息:{}","welcome log world");
return "主表:"+testService.queryCountByMester();
}
@RequestMapping("/test1")
public String test(@RequestParam String name) {
return "從表:"+testService.queryCountBySavle(name);
}
}
7.DataSourceAspect
package com.cxb.database.datasource;
import java.lang.reflect.Method;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
* AOP根據註解給上下文賦值
*/
@Aspect
// 設置AOP執行順序(需要在事務之前,否則事務只發生在默認庫中)
@Order(1)
@Component
public class DataSourceAspect {
private Logger logger = LoggerFactory.getLogger(this.getClass());
//切點
@Pointcut("execution(* com.cxb.database.service..*.*(..)))")
public void aspect() { }
@Before("aspect()")
private void before(JoinPoint point) {
Object target = point.getTarget();
String method = point.getSignature().getName();
Class<?> classz = target.getClass();
Class<?>[] parameterTypes = ((MethodSignature) point.getSignature())
.getMethod().getParameterTypes();
try {
Method m = classz.getMethod(method, parameterTypes);
if (m != null && m.isAnnotationPresent(MyDataSource.class)) {
MyDataSource data = m.getAnnotation(MyDataSource.class);
JdbcContextHolder.putDataSource(data.value().getName());
logger.info("===============上下文賦值完成:{}",data.value().getName());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
8.DataSourceConfig
package com.cxb.database.datasource;
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
/**
* 數據源配置
*/
@Configuration
public class DataSourceConfig {
@Bean(name = "master")
@ConfigurationProperties(prefix = "datasource.master")
public DataSource dataSource1() {
System.out.println("主配");
return DataSourceBuilder.create().build();
}
@Bean(name = "slave")
@ConfigurationProperties(prefix = "datasource.slave")
public DataSource dataSource2() {
System.out.println("從配");
return DataSourceBuilder.create().build();
}
@Bean(name="dynamicDataSource")
@Primary //優先使用,多數據源
public DataSource dataSource() {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
DataSource master = dataSource1();
DataSource slave = dataSource2();
//設置默認數據源
dynamicDataSource.setDefaultTargetDataSource(master);
//配置多數據源
Map<Object,Object> map = new HashMap<>();
map.put(DataSourceType.Master.getName(), master); //key需要跟ThreadLocal中的值對應
map.put(DataSourceType.Slave.getName(), slave);
dynamicDataSource.setTargetDataSources(map);
return dynamicDataSource;
}
}
9.DataSourceType
package com.cxb.database.datasource;
public enum DataSourceType {
// 主表
Master("master"),
// 從表
Slave("slave");
private String name;
private DataSourceType(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
10.DynamicDataSource
package com.cxb.database.datasource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDataSource extends AbstractRoutingDataSource {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
protected Object determineCurrentLookupKey() {
logger.info("數據源爲{}",JdbcContextHolder.getDataSource());
return JdbcContextHolder.getDataSource();
}
}
11.JdbcContextHolder
package com.cxb.database.datasource;
/**
* 動態數據源的上下文 threadlocal
*/
public class JdbcContextHolder {
private final static ThreadLocal<String> local = new ThreadLocal<>();
public static void putDataSource(String name) {
local.set(name);
}
public static String getDataSource() {
return local.get();
}
}
12.MyDataSource
package com.cxb.database.datasource;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 數據源選擇--自定義註解
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyDataSource {
DataSourceType value() default DataSourceType.Master; //默認主表
}
13.TestMapper
package com.cxb.database.mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Update;
public interface TestMapper {
Integer queryCount();
@Update("update book set stock = stock - 1 where name = #{name} and stock != 0")
Integer updateBookByName(@Param("name") String name);
}
14.TestService
package com.cxb.database.service;
public interface TestService {
Integer queryCountByMester();
Integer queryCountBySavle(String name);
}
15.TestServiceImpl
package com.cxb.database.service.impl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.cxb.database.datasource.DataSourceType;
import com.cxb.database.datasource.MyDataSource;
import com.cxb.database.mapper.TestMapper;
import com.cxb.database.service.TestService;
@Service
public class TestServiceImpl implements TestService {
Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private TestMapper testMapper;
@Override
@MyDataSource(DataSourceType.Master)
public Integer queryCountByMester() {
return testMapper.queryCount();
}
@Override
@MyDataSource(DataSourceType.Slave)
@Transactional
public Integer queryCountBySavle(String name) {
//測試事務
Integer rows = testMapper.updateBookByName(name);
if (rows <= 0) {
logger.info("更新小於1 執行回滾");
throw new RuntimeException();
}
return rows;
}
}
16.Application
package com.cxb.database;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@SpringBootApplication(exclude = {
DataSourceAutoConfiguration.class
})
//設置事務執行順序(需要在切換數據源之後,否則只走主庫)
@EnableTransactionManagement(order = 2)
@MapperScan(basePackages = "com.cxb.database.mapper")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
最後測試: