SpringBoot實現多數據源、AOP、主從分離

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);
	}
}

最後測試:

代碼下載

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章